/*
 #
 #  File            : CImg.h
 #                    ( C++ header file )
 #
 #  Description     : The C++ Template Image Processing Library.
 #                    This file is the main part of the CImg Library project.
 #                    ( http://cimg.sourceforge.net )
 #
 #  Project manager : David Tschumperle.
 #                    ( http://www.greyc.ensicaen.fr/~dtschump/ )
 #
 #                    The complete contributor list can be seen in the 'README.txt' file.
 #
 #  Licenses        : This file is "dual-licensed", you have to choose one
 #                    of the two licenses below to apply on this file.
 #
 #                    CeCILL-C
 #                    The CeCILL-C license is close to the GNU LGPL.
 #                    ( http://www.cecill.info/licences/Licence_CeCILL-C_V1-en.html )
 #
 #                or  CeCILL v2.0
 #                    The CeCILL license is compatible with the GNU GPL.
 #                    ( http://www.cecill.info/licences/Licence_CeCILL_V2-en.html )
 #
 #  This software is governed either by the CeCILL or the CeCILL-C license
 #  under French law and abiding by the rules of distribution of free software.
 #  You can  use, modify and or redistribute the software under the terms of
 #  the CeCILL or CeCILL-C licenses as circulated by CEA, CNRS and INRIA
 #  at the following URL : "http://www.cecill.info".
 #
 #  As a counterpart to the access to the source code and  rights to copy,
 #  modify and redistribute granted by the license, users are provided only
 #  with a limited warranty  and the software's author,  the holder of the
 #  economic rights,  and the successive licensors  have only  limited
 #  liability.
 #
 #  In this respect, the user's attention is drawn to the risks associated
 #  with loading,  using,  modifying and/or developing or reproducing the
 #  software by the user in light of its specific status of free software,
 #  that may mean  that it is complicated to manipulate,  and  that  also
 #  therefore means  that it is reserved for developers  and  experienced
 #  professionals having in-depth computer knowledge. Users are therefore
 #  encouraged to load and test the software's suitability as regards their
 #  requirements in conditions enabling the security of their systems and/or
 #  data to be ensured and,  more generally, to use and operate it in the
 #  same conditions as regards security.
 #
 #  The fact that you are presently reading this means that you have had
 #  knowledge of the CeCILL and CeCILL-C licenses and that you accept its terms.
 #
*/

// Define version number of the current file.
//
#ifndef cimg_version
#define cimg_version 130

/*-----------------------------------------------------------
 #
 # Test/auto-set CImg configuration variables
 # and include required headers.
 #
 # If you find that default configuration variables are
 # not adapted, you can override their values before including
 # the header file "CImg.h" (using the #define directive).
 #
 ------------------------------------------------------------*/

// Include required standard C++ headers.
//
#include <cstdio>
#include <cstdlib>
#include <cstdarg>
#include <cstring>
#include <cmath>
#include <ctime>

// Operating system configuration.
//
// Define 'cimg_OS' to : 0 for an unknown OS (will try to minize library dependancies).
//                       1 for a Unix-like OS (Linux, Solaris, BSD, MacOSX, Irix, ...).
//                       2 for Microsoft Windows.
//
#ifndef cimg_OS
#if defined(unix)        || defined(__unix)      || defined(__unix__) \
 || defined(linux)       || defined(__linux)     || defined(__linux__) \
 || defined(sun)         || defined(__sun) \
 || defined(BSD)         || defined(__OpenBSD__) || defined(__NetBSD__) \
 || defined(__FreeBSD__) || defined __DragonFly__ \
 || defined(sgi)         || defined(__sgi) \
 || defined(__MACOSX__)  || defined(__APPLE__) \
 || defined(__CYGWIN__)
#define cimg_OS 1
#elif defined(_MSC_VER) || defined(WIN32)  || defined(_WIN32) || defined(__WIN32__) \
   || defined(WIN64)    || defined(_WIN64) || defined(__WIN64__)
#define cimg_OS 2
#else
#define cimg_OS 0
#endif
#elif !(cimg_OS==0 || cimg_OS==1 || cimg_OS==2)
#error CImg Library : Configuration variable 'cimg_OS' is badly defined.
#error (valid values are '0=unknown OS', '1=Unix-like OS', '2=Microsoft Windows').
#endif

// Compiler configuration.
//
// Try to detect Microsoft VC++ compilers.
// (lot of workarounds are needed afterwards to
// make CImg working, particularly with VC++ 6.0).
//
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable:4311)
#pragma warning(disable:4312)
#pragma warning(disable:4800)
#pragma warning(disable:4804)
#pragma warning(disable:4996)
#ifndef _CRT_SECURE_NO_DEPRECATE
#   define _CRT_SECURE_NO_DEPRECATE 1
#endif
#ifndef _CRT_NONSTDC_NO_DEPRECATE
#   define _CRT_NONSTDC_NO_DEPRECATE 1
#endif
#if _MSC_VER<1300
#define cimg_use_visualcpp6
#define cimg_std
#define _WIN32_WINNT 0x0500
#endif
#endif

// Include OS-specific headers.
//
#if cimg_OS==1
#include <sys/time.h>
#include <unistd.h>
#elif cimg_OS==2
#include <windows.h>
#ifndef _WIN32_IE
#define _WIN32_IE 0x0400
#endif
#include <shlobj.h>
#endif

// Define defaut pipe for output messages
//
// Define 'cimg_stdout' to : stdout to print CImg messages on the standard output.
//                           stderr to print CImg messages on the standart error output (default behavior).
//
#ifndef cimg_std
#define cimg_std std
#endif
#ifndef cimg_stdout
#define cimg_stdout stderr
#endif

// Output messages configuration.
//
// Define 'cimg_debug' to : 0 to hide debug messages (quiet mode, but exceptions are still thrown).
//                          1 to display debug messages on the console.
//                          2 to display debug messages with dialog windows (default behavior).
//                          3 to do as 1 + add extra warnings (may slow down the code !).
//                          4 to do as 2 + add extra warnings (may slow down the code !).
//
// Define 'cimg_strict_warnings' to replace warning messages by exception throwns.
//
// Define 'cimg_use_vt100' to allow output of color messages (require VT100-compatible terminal).
//
#ifndef cimg_debug
#define cimg_debug 2
#elif !(cimg_debug==0 || cimg_debug==1 || cimg_debug==2 || cimg_debug==3 || cimg_debug==4)
#error CImg Library : Configuration variable 'cimg_debug' is badly defined.
#error (valid values are '0=quiet', '1=console', '2=dialog', '3=console+warnings', '4=dialog+warnings').
#endif

// Display framework configuration.
//
// Define 'cimg_display' to : 0 to disable display capabilities.
//                            1 to use X-Window framework (X11).
//                            2 to use Microsoft GDI32 framework.
//                            3 to use Apple Carbon framework.
//
#ifndef cimg_display
#if cimg_OS==0
#define cimg_display 0
#elif cimg_OS==1
#if defined(__MACOSX__) || defined(__APPLE__)
#define cimg_display 1
#else
#define cimg_display 1
#endif
#elif cimg_OS==2
#define cimg_display 2
#endif
#elif !(cimg_display==0 || cimg_display==1 || cimg_display==2 || cimg_display==3)
#error CImg Library : Configuration variable 'cimg_display' is badly defined.
#error (valid values are '0=disable', '1=X-Window (X11)', '2=Microsoft GDI32', '3=Apple Carbon').
#endif

// Include display-specific headers.
//
#if cimg_display==1
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/keysym.h>
#include <pthread.h>
#ifdef cimg_use_xshm
#include <sys/ipc.h>
#include <sys/shm.h>
#include <X11/extensions/XShm.h>
#endif
#ifdef cimg_use_xrandr
#include <X11/extensions/Xrandr.h>
#endif
#elif cimg_display==3
#include <Carbon/Carbon.h>
#include <pthread.h>
#endif

// OpenMP configuration.
// (http://www.openmp.org)
//
// Define 'cimg_use_openmp' to enable OpenMP support.
//
// OpenMP directives can be used in few CImg functions to get
// advantages of multi-core CPUs. Using OpenMP is not mandatory.
//
#ifdef cimg_use_openmp
#include "omp.h"
#endif

// LibPNG configuration.
// (http://www.libpng.org)
//
// Define 'cimg_use_png' to enable LibPNG support.
//
// LibPNG can be used in functions 'CImg<T>::{load,save}_png()'
// to get a builtin support of PNG files. Using LibPNG is not mandatory.
//
#ifdef cimg_use_png
extern "C" {
#include "png.h"
}
#endif

// LibJPEG configuration.
// (http://en.wikipedia.org/wiki/Libjpeg)
//
// Define 'cimg_use_jpeg' to enable LibJPEG support.
//
// LibJPEG can be used in functions 'CImg<T>::{load,save}_jpeg()'
// to get a builtin support of JPEG files. Using LibJPEG is not mandatory.
//
#ifdef cimg_use_jpeg
extern "C" {
#include "jpeglib.h"
}
#endif

// LibTIFF configuration.
// (http://www.libtiff.org)
//
// Define 'cimg_use_tiff' to enable LibTIFF support.
//
// LibTIFF can be used in functions 'CImg[List]<T>::{load,save}_tiff()'
// to get a builtin support of TIFF files. Using LibTIFF is not mandatory.
//
#ifdef cimg_use_tiff
extern "C" {
#include "tiffio.h"
}
#endif

// FFMPEG Avcodec and Avformat libraries configuration.
// (http://www.ffmpeg.org)
//
// Define 'cimg_use_ffmpeg' to enable FFMPEG lib support.
//
// Avcodec and Avformat libraries can be used in functions
// 'CImg[List]<T>::load_ffmpeg()' to get a builtin
// support of various image sequences files.
// Using FFMPEG libraries is not mandatory.
//
#ifdef cimg_use_ffmpeg
extern "C" {
#include "avformat.h"
#include "avcodec.h"
#include "swscale.h"
}
#endif

// Zlib configuration
// (http://www.zlib.net)
//
// Define 'cimg_use_zlib' to enable Zlib support.
//
// Zlib can be used in functions 'CImg[List]<T>::{load,save}_cimg()'
// to allow compressed data in '.cimg' files. Using Zlib is not mandatory.
//
#ifdef cimg_use_zlib
extern "C" {
#include "zlib.h"
}
#endif

// Magick++ configuration.
// (http://www.imagemagick.org/Magick++)
//
// Define 'cimg_use_magick' to enable Magick++ support.
//
// Magick++ library can be used in functions 'CImg<T>::{load,save}()'
// to get a builtin support of various image formats (PNG,JPEG,TIFF,...).
// Using Magick++ is not mandatory.
//
#ifdef cimg_use_magick
#include "Magick++.h"
#endif

// FFTW3 configuration.
// (http://www.fftw.org)
//
// Define 'cimg_use_fftw3' to enable libFFTW3 support.
//
// FFTW3 library can be used in functions 'CImg[List]<T>::FFT()' to
// efficiently compile the Fast Fourier Transform of image data.
//
#ifdef cimg_use_fftw3
extern "C" {
#include "fftw3.h"
}
#endif

// Board configuration.
// (http://libboard.sourceforge.net/)
//
// Define 'cimg_use_board' to enable Board support.
//
// Board library can be used in functions 'CImg<T>::draw_object3d()'
// to draw objects 3D in vector-graphics canvas that can be saved
// as .PS or .SVG files afterwards.
//
#ifdef cimg_use_board
#include "Board.h"
#endif

// Lapack configuration.
// (http://www.netlib.org/lapack)
//
// Define 'cimg_use_lapack' to enable LAPACK support.
//
// Lapack can be used in various CImg functions dealing with
// matrix computation and algorithms (eigenvalues, inverse, ...).
// Using Lapack is not mandatory.
//
#ifdef cimg_use_lapack
extern "C" {
  extern void sgetrf_(int*, int*, float*, int*, int*, int*);
  extern void sgetri_(int*, float*, int*, int*, float*, int*, int*);
  extern void sgetrs_(char*, int*, int*, float*, int*, int*, float*, int*, int*);
  extern void sgesvd_(char*, char*, int*, int*, float*, int*, float*, float*, int*, float*, int*, float*, int*, int*);
  extern void ssyev_(char*, char*, int*, float*, int*, float*, float*, int*, int*);
  extern void dgetrf_(int*, int*, double*, int*, int*, int*);
  extern void dgetri_(int*, double*, int*, int*, double*, int*, int*);
  extern void dgetrs_(char*, int*, int*, double*, int*, int*, double*, int*, int*);
  extern void dgesvd_(char*, char*, int*, int*, double*, int*, double*, double*, int*, double*, int*, double*, int*, int*);
  extern void dsyev_(char*, char*, int*, double*, int*, double*, double*, int*, int*);
}
#endif

// Check if min/max macros are defined.
//
// CImg does not compile if macros 'min' or 'max' are defined,
// because min() and max() functions are also defined in the cimg:: namespace.
// so it '#undef' these macros if necessary, and restore them to reasonable
// values at the end of the file.
//
#ifdef min
#undef min
#define _cimg_redefine_min
#endif
#ifdef max
#undef max
#define _cimg_redefine_max
#endif

// Set the current working directory for native MacOSX bundled applications.
//
// By default, MacOS bundled applications set the cwd at the root directory '/',
// the code below allows to set it to the current exec directory instead when
// a CImg-based program is executed.
//
#if cimg_OS==1 && cimg_display==3
static struct _cimg_macosx_setcwd {
  _cimg_macosx_setcwd() {
    FSRef location;
    ProcessSerialNumber psn;
    char filePath[512];
    if (GetCurrentProcess(&psn)!=noErr) return;
    if (GetProcessBundleLocation(&psn,&location)!=noErr) return;
    FSRefMakePath(&location,(UInt8*)filePath,sizeof(filePath)-1);
    int p = cimg_std::strlen(filePath);
    while (filePath[p] != '/') --p;
    filePath[p] = 0;
    chdir(filePath);
  }
} cimg_macosx_setcwd;
#endif

/*------------------------------------------------------------------------------
  #
  # Define user-friendly macros.
  #
  # User macros are prefixed by 'cimg_' and can be used in your own code.
  # They are particularly useful for option parsing, and image loops creation.
  #
  ------------------------------------------------------------------------------*/

// Define the program usage, and retrieve command line arguments.
//
#define cimg_usage(usage) cimg_library::cimg::option((char*)0,argc,argv,(char*)0,usage)
#define cimg_help(str)    cimg_library::cimg::option((char*)0,argc,argv,str,(char*)0)
#define cimg_option(name,defaut,usage) cimg_library::cimg::option(name,argc,argv,defaut,usage)
#define cimg_argument(pos) cimg_library::cimg::argument(pos,argc,argv)
#define cimg_argument1(pos,s0) cimg_library::cimg::argument(pos,argc,argv,1,s0)
#define cimg_argument2(pos,s0,s1) cimg_library::cimg::argument(pos,argc,argv,2,s0,s1)
#define cimg_argument3(pos,s0,s1,s2) cimg_library::cimg::argument(pos,argc,argv,3,s0,s1,s2)
#define cimg_argument4(pos,s0,s1,s2,s3) cimg_library::cimg::argument(pos,argc,argv,4,s0,s1,s2,s3)
#define cimg_argument5(pos,s0,s1,s2,s3,s4) cimg_library::cimg::argument(pos,argc,argv,5,s0,s1,s2,s3,s4)
#define cimg_argument6(pos,s0,s1,s2,s3,s4,s5) cimg_library::cimg::argument(pos,argc,argv,6,s0,s1,s2,s3,s4,s5)
#define cimg_argument7(pos,s0,s1,s2,s3,s4,s5,s6) cimg_library::cimg::argument(pos,argc,argv,7,s0,s1,s2,s3,s4,s5,s6)
#define cimg_argument8(pos,s0,s1,s2,s3,s4,s5,s6,s7) cimg_library::cimg::argument(pos,argc,argv,8,s0,s1,s2,s3,s4,s5,s6,s7)
#define cimg_argument9(pos,s0,s1,s2,s3,s4,s5,s6,s7,s8) cimg_library::cimg::argument(pos,argc,argv,9,s0,s1,s2,s3,s4,s5,s6,s7,s8)

// Define and manipulate local neighborhoods.
//
#define CImg_2x2(I,T) T I[4]; \
                      T& I##cc = I[0]; T& I##nc = I[1]; \
                      T& I##cn = I[2]; T& I##nn = I[3]; \
                      I##cc = I##nc = \
                      I##cn = I##nn = 0

#define CImg_3x3(I,T) T I[9]; \
                      T& I##pp = I[0]; T& I##cp = I[1]; T& I##np = I[2]; \
                      T& I##pc = I[3]; T& I##cc = I[4]; T& I##nc = I[5]; \
                      T& I##pn = I[6]; T& I##cn = I[7]; T& I##nn = I[8]; \
                      I##pp = I##cp = I##np = \
                      I##pc = I##cc = I##nc = \
                      I##pn = I##cn = I##nn = 0

#define CImg_4x4(I,T) T I[16]; \
                      T& I##pp = I[0]; T& I##cp = I[1]; T& I##np = I[2]; T& I##ap = I[3]; \
                      T& I##pc = I[4]; T& I##cc = I[5]; T& I##nc = I[6]; T& I##ac = I[7]; \
                      T& I##pn = I[8]; T& I##cn = I[9]; T& I##nn = I[10]; T& I##an = I[11]; \
                      T& I##pa = I[12]; T& I##ca = I[13]; T& I##na = I[14]; T& I##aa = I[15]; \
                      I##pp = I##cp = I##np = I##ap = \
                      I##pc = I##cc = I##nc = I##ac = \
                      I##pn = I##cn = I##nn = I##an = \
                      I##pa = I##ca = I##na = I##aa = 0

#define CImg_5x5(I,T) T I[25]; \
                      T& I##bb = I[0]; T& I##pb = I[1]; T& I##cb = I[2]; T& I##nb = I[3]; T& I##ab = I[4]; \
                      T& I##bp = I[5]; T& I##pp = I[6]; T& I##cp = I[7]; T& I##np = I[8]; T& I##ap = I[9]; \
                      T& I##bc = I[10]; T& I##pc = I[11]; T& I##cc = I[12]; T& I##nc = I[13]; T& I##ac = I[14]; \
                      T& I##bn = I[15]; T& I##pn = I[16]; T& I##cn = I[17]; T& I##nn = I[18]; T& I##an = I[19]; \
                      T& I##ba = I[20]; T& I##pa = I[21]; T& I##ca = I[22]; T& I##na = I[23]; T& I##aa = I[24]; \
                      I##bb = I##pb = I##cb = I##nb = I##ab = \
                      I##bp = I##pp = I##cp = I##np = I##ap = \
                      I##bc = I##pc = I##cc = I##nc = I##ac = \
                      I##bn = I##pn = I##cn = I##nn = I##an = \
                      I##ba = I##pa = I##ca = I##na = I##aa = 0

#define CImg_2x2x2(I,T) T I[8]; \
                      T& I##ccc = I[0]; T& I##ncc = I[1]; \
                      T& I##cnc = I[2]; T& I##nnc = I[3]; \
                      T& I##ccn = I[4]; T& I##ncn = I[5]; \
                      T& I##cnn = I[6]; T& I##nnn = I[7]; \
                      I##ccc = I##ncc = \
                      I##cnc = I##nnc = \
                      I##ccn = I##ncn = \
                      I##cnn = I##nnn = 0

#define CImg_3x3x3(I,T) T I[27]; \
                      T& I##ppp = I[0]; T& I##cpp = I[1]; T& I##npp = I[2]; \
                      T& I##pcp = I[3]; T& I##ccp = I[4]; T& I##ncp = I[5]; \
                      T& I##pnp = I[6]; T& I##cnp = I[7]; T& I##nnp = I[8]; \
                      T& I##ppc = I[9]; T& I##cpc = I[10]; T& I##npc = I[11]; \
                      T& I##pcc = I[12]; T& I##ccc = I[13]; T& I##ncc = I[14]; \
                      T& I##pnc = I[15]; T& I##cnc = I[16]; T& I##nnc = I[17]; \
                      T& I##ppn = I[18]; T& I##cpn = I[19]; T& I##npn = I[20]; \
                      T& I##pcn = I[21]; T& I##ccn = I[22]; T& I##ncn = I[23]; \
                      T& I##pnn = I[24]; T& I##cnn = I[25]; T& I##nnn = I[26]; \
                      I##ppp = I##cpp = I##npp = \
                      I##pcp = I##ccp = I##ncp = \
                      I##pnp = I##cnp = I##nnp = \
                      I##ppc = I##cpc = I##npc = \
                      I##pcc = I##ccc = I##ncc = \
                      I##pnc = I##cnc = I##nnc = \
                      I##ppn = I##cpn = I##npn = \
                      I##pcn = I##ccn = I##ncn = \
                      I##pnn = I##cnn = I##nnn = 0

#define cimg_get2x2(img,x,y,z,v,I) \
  I[0] = (img)(x,y,z,v), I[1] = (img)(_n1##x,y,z,v), I[2] = (img)(x,_n1##y,z,v), I[3] = (img)(_n1##x,_n1##y,z,v)

#define cimg_get3x3(img,x,y,z,v,I) \
  I[0] = (img)(_p1##x,_p1##y,z,v), I[1] = (img)(x,_p1##y,z,v), I[2] = (img)(_n1##x,_p1##y,z,v), I[3] = (img)(_p1##x,y,z,v), \
  I[4] = (img)(x,y,z,v), I[5] = (img)(_n1##x,y,z,v), I[6] = (img)(_p1##x,_n1##y,z,v), I[7] = (img)(x,_n1##y,z,v), \
  I[8] = (img)(_n1##x,_n1##y,z,v)

#define cimg_get4x4(img,x,y,z,v,I) \
  I[0] = (img)(_p1##x,_p1##y,z,v), I[1] = (img)(x,_p1##y,z,v), I[2] = (img)(_n1##x,_p1##y,z,v), I[3] = (img)(_n2##x,_p1##y,z,v), \
  I[4] = (img)(_p1##x,y,z,v), I[5] = (img)(x,y,z,v), I[6] = (img)(_n1##x,y,z,v), I[7] = (img)(_n2##x,y,z,v), \
  I[8] = (img)(_p1##x,_n1##y,z,v), I[9] = (img)(x,_n1##y,z,v), I[10] = (img)(_n1##x,_n1##y,z,v), I[11] = (img)(_n2##x,_n1##y,z,v), \
  I[12] = (img)(_p1##x,_n2##y,z,v), I[13] = (img)(x,_n2##y,z,v), I[14] = (img)(_n1##x,_n2##y,z,v), I[15] = (img)(_n2##x,_n2##y,z,v)

#define cimg_get5x5(img,x,y,z,v,I) \
  I[0] = (img)(_p2##x,_p2##y,z,v), I[1] = (img)(_p1##x,_p2##y,z,v), I[2] = (img)(x,_p2##y,z,v), I[3] = (img)(_n1##x,_p2##y,z,v), \
  I[4] = (img)(_n2##x,_p2##y,z,v), I[5] = (img)(_p2##x,_p1##y,z,v), I[6] = (img)(_p1##x,_p1##y,z,v), I[7] = (img)(x,_p1##y,z,v), \
  I[8] = (img)(_n1##x,_p1##y,z,v), I[9] = (img)(_n2##x,_p1##y,z,v), I[10] = (img)(_p2##x,y,z,v), I[11] = (img)(_p1##x,y,z,v), \
  I[12] = (img)(x,y,z,v), I[13] = (img)(_n1##x,y,z,v), I[14] = (img)(_n2##x,y,z,v), I[15] = (img)(_p2##x,_n1##y,z,v), \
  I[16] = (img)(_p1##x,_n1##y,z,v), I[17] = (img)(x,_n1##y,z,v), I[18] = (img)(_n1##x,_n1##y,z,v), I[19] = (img)(_n2##x,_n1##y,z,v), \
  I[20] = (img)(_p2##x,_n2##y,z,v), I[21] = (img)(_p1##x,_n2##y,z,v), I[22] = (img)(x,_n2##y,z,v), I[23] = (img)(_n1##x,_n2##y,z,v), \
  I[24] = (img)(_n2##x,_n2##y,z,v)

#define cimg_get6x6(img,x,y,z,v,I) \
 I[0] = (img)(_p2##x,_p2##y,z,v), I[1] = (img)(_p1##x,_p2##y,z,v), I[2] = (img)(x,_p2##y,z,v), I[3] = (img)(_n1##x,_p2##y,z,v), \
 I[4] = (img)(_n2##x,_p2##y,z,v), I[5] = (img)(_n3##x,_p2##y,z,v), I[6] = (img)(_p2##x,_p1##y,z,v), I[7] = (img)(_p1##x,_p1##y,z,v), \
 I[8] = (img)(x,_p1##y,z,v), I[9] = (img)(_n1##x,_p1##y,z,v), I[10] = (img)(_n2##x,_p1##y,z,v), I[11] = (img)(_n3##x,_p1##y,z,v), \
 I[12] = (img)(_p2##x,y,z,v), I[13] = (img)(_p1##x,y,z,v), I[14] = (img)(x,y,z,v), I[15] = (img)(_n1##x,y,z,v), \
 I[16] = (img)(_n2##x,y,z,v), I[17] = (img)(_n3##x,y,z,v), I[18] = (img)(_p2##x,_n1##y,z,v), I[19] = (img)(_p1##x,_n1##y,z,v), \
 I[20] = (img)(x,_n1##y,z,v), I[21] = (img)(_n1##x,_n1##y,z,v), I[22] = (img)(_n2##x,_n1##y,z,v), I[23] = (img)(_n3##x,_n1##y,z,v), \
 I[24] = (img)(_p2##x,_n2##y,z,v), I[25] = (img)(_p1##x,_n2##y,z,v), I[26] = (img)(x,_n2##y,z,v), I[27] = (img)(_n1##x,_n2##y,z,v), \
 I[28] = (img)(_n2##x,_n2##y,z,v), I[29] = (img)(_n3##x,_n2##y,z,v), I[30] = (img)(_p2##x,_n3##y,z,v), I[31] = (img)(_p1##x,_n3##y,z,v), \
 I[32] = (img)(x,_n3##y,z,v), I[33] = (img)(_n1##x,_n3##y,z,v), I[34] = (img)(_n2##x,_n3##y,z,v), I[35] = (img)(_n3##x,_n3##y,z,v)

#define cimg_get7x7(img,x,y,z,v,I) \
 I[0] = (img)(_p3##x,_p3##y,z,v), I[1] = (img)(_p2##x,_p3##y,z,v), I[2] = (img)(_p1##x,_p3##y,z,v), I[3] = (img)(x,_p3##y,z,v), \
 I[4] = (img)(_n1##x,_p3##y,z,v), I[5] = (img)(_n2##x,_p3##y,z,v), I[6] = (img)(_n3##x,_p3##y,z,v), I[7] = (img)(_p3##x,_p2##y,z,v), \
 I[8] = (img)(_p2##x,_p2##y,z,v), I[9] = (img)(_p1##x,_p2##y,z,v), I[10] = (img)(x,_p2##y,z,v), I[11] = (img)(_n1##x,_p2##y,z,v), \
 I[12] = (img)(_n2##x,_p2##y,z,v), I[13] = (img)(_n3##x,_p2##y,z,v), I[14] = (img)(_p3##x,_p1##y,z,v), I[15] = (img)(_p2##x,_p1##y,z,v), \
 I[16] = (img)(_p1##x,_p1##y,z,v), I[17] = (img)(x,_p1##y,z,v), I[18] = (img)(_n1##x,_p1##y,z,v), I[19] = (img)(_n2##x,_p1##y,z,v), \
 I[20] = (img)(_n3##x,_p1##y,z,v), I[21] = (img)(_p3##x,y,z,v), I[22] = (img)(_p2##x,y,z,v), I[23] = (img)(_p1##x,y,z,v), \
 I[24] = (img)(x,y,z,v), I[25] = (img)(_n1##x,y,z,v), I[26] = (img)(_n2##x,y,z,v), I[27] = (img)(_n3##x,y,z,v), \
 I[28] = (img)(_p3##x,_n1##y,z,v), I[29] = (img)(_p2##x,_n1##y,z,v), I[30] = (img)(_p1##x,_n1##y,z,v), I[31] = (img)(x,_n1##y,z,v), \
 I[32] = (img)(_n1##x,_n1##y,z,v), I[33] = (img)(_n2##x,_n1##y,z,v), I[34] = (img)(_n3##x,_n1##y,z,v), I[35] = (img)(_p3##x,_n2##y,z,v), \
 I[36] = (img)(_p2##x,_n2##y,z,v), I[37] = (img)(_p1##x,_n2##y,z,v), I[38] = (img)(x,_n2##y,z,v), I[39] = (img)(_n1##x,_n2##y,z,v), \
 I[40] = (img)(_n2##x,_n2##y,z,v), I[41] = (img)(_n3##x,_n2##y,z,v), I[42] = (img)(_p3##x,_n3##y,z,v), I[43] = (img)(_p2##x,_n3##y,z,v), \
 I[44] = (img)(_p1##x,_n3##y,z,v), I[45] = (img)(x,_n3##y,z,v), I[46] = (img)(_n1##x,_n3##y,z,v), I[47] = (img)(_n2##x,_n3##y,z,v), \
 I[48] = (img)(_n3##x,_n3##y,z,v)

#define cimg_get8x8(img,x,y,z,v,I) \
 I[0] = (img)(_p3##x,_p3##y,z,v), I[1] = (img)(_p2##x,_p3##y,z,v), I[2] = (img)(_p1##x,_p3##y,z,v), I[3] = (img)(x,_p3##y,z,v), \
 I[4] = (img)(_n1##x,_p3##y,z,v), I[5] = (img)(_n2##x,_p3##y,z,v), I[6] = (img)(_n3##x,_p3##y,z,v), I[7] = (img)(_n4##x,_p3##y,z,v), \
 I[8] = (img)(_p3##x,_p2##y,z,v), I[9] = (img)(_p2##x,_p2##y,z,v), I[10] = (img)(_p1##x,_p2##y,z,v), I[11] = (img)(x,_p2##y,z,v), \
 I[12] = (img)(_n1##x,_p2##y,z,v), I[13] = (img)(_n2##x,_p2##y,z,v), I[14] = (img)(_n3##x,_p2##y,z,v), I[15] = (img)(_n4##x,_p2##y,z,v), \
 I[16] = (img)(_p3##x,_p1##y,z,v), I[17] = (img)(_p2##x,_p1##y,z,v), I[18] = (img)(_p1##x,_p1##y,z,v), I[19] = (img)(x,_p1##y,z,v), \
 I[20] = (img)(_n1##x,_p1##y,z,v), I[21] = (img)(_n2##x,_p1##y,z,v), I[22] = (img)(_n3##x,_p1##y,z,v), I[23] = (img)(_n4##x,_p1##y,z,v), \
 I[24] = (img)(_p3##x,y,z,v), I[25] = (img)(_p2##x,y,z,v), I[26] = (img)(_p1##x,y,z,v), I[27] = (img)(x,y,z,v), \
 I[28] = (img)(_n1##x,y,z,v), I[29] = (img)(_n2##x,y,z,v), I[30] = (img)(_n3##x,y,z,v), I[31] = (img)(_n4##x,y,z,v), \
 I[32] = (img)(_p3##x,_n1##y,z,v), I[33] = (img)(_p2##x,_n1##y,z,v), I[34] = (img)(_p1##x,_n1##y,z,v), I[35] = (img)(x,_n1##y,z,v), \
 I[36] = (img)(_n1##x,_n1##y,z,v), I[37] = (img)(_n2##x,_n1##y,z,v), I[38] = (img)(_n3##x,_n1##y,z,v), I[39] = (img)(_n4##x,_n1##y,z,v), \
 I[40] = (img)(_p3##x,_n2##y,z,v), I[41] = (img)(_p2##x,_n2##y,z,v), I[42] = (img)(_p1##x,_n2##y,z,v), I[43] = (img)(x,_n2##y,z,v), \
 I[44] = (img)(_n1##x,_n2##y,z,v), I[45] = (img)(_n2##x,_n2##y,z,v), I[46] = (img)(_n3##x,_n2##y,z,v), I[47] = (img)(_n4##x,_n2##y,z,v), \
 I[48] = (img)(_p3##x,_n3##y,z,v), I[49] = (img)(_p2##x,_n3##y,z,v), I[50] = (img)(_p1##x,_n3##y,z,v), I[51] = (img)(x,_n3##y,z,v), \
 I[52] = (img)(_n1##x,_n3##y,z,v), I[53] = (img)(_n2##x,_n3##y,z,v), I[54] = (img)(_n3##x,_n3##y,z,v), I[55] = (img)(_n4##x,_n3##y,z,v), \
 I[56] = (img)(_p3##x,_n4##y,z,v), I[57] = (img)(_p2##x,_n4##y,z,v), I[58] = (img)(_p1##x,_n4##y,z,v), I[59] = (img)(x,_n4##y,z,v), \
 I[60] = (img)(_n1##x,_n4##y,z,v), I[61] = (img)(_n2##x,_n4##y,z,v), I[62] = (img)(_n3##x,_n4##y,z,v), I[63] = (img)(_n4##x,_n4##y,z,v);

#define cimg_get9x9(img,x,y,z,v,I) \
 I[0] = (img)(_p4##x,_p4##y,z,v), I[1] = (img)(_p3##x,_p4##y,z,v), I[2] = (img)(_p2##x,_p4##y,z,v), I[3] = (img)(_p1##x,_p4##y,z,v), \
 I[4] = (img)(x,_p4##y,z,v), I[5] = (img)(_n1##x,_p4##y,z,v), I[6] = (img)(_n2##x,_p4##y,z,v), I[7] = (img)(_n3##x,_p4##y,z,v), \
 I[8] = (img)(_n4##x,_p4##y,z,v), I[9] = (img)(_p4##x,_p3##y,z,v), I[10] = (img)(_p3##x,_p3##y,z,v), I[11] = (img)(_p2##x,_p3##y,z,v), \
 I[12] = (img)(_p1##x,_p3##y,z,v), I[13] = (img)(x,_p3##y,z,v), I[14] = (img)(_n1##x,_p3##y,z,v), I[15] = (img)(_n2##x,_p3##y,z,v), \
 I[16] = (img)(_n3##x,_p3##y,z,v), I[17] = (img)(_n4##x,_p3##y,z,v), I[18] = (img)(_p4##x,_p2##y,z,v), I[19] = (img)(_p3##x,_p2##y,z,v), \
 I[20] = (img)(_p2##x,_p2##y,z,v), I[21] = (img)(_p1##x,_p2##y,z,v), I[22] = (img)(x,_p2##y,z,v), I[23] = (img)(_n1##x,_p2##y,z,v), \
 I[24] = (img)(_n2##x,_p2##y,z,v), I[25] = (img)(_n3##x,_p2##y,z,v), I[26] = (img)(_n4##x,_p2##y,z,v), I[27] = (img)(_p4##x,_p1##y,z,v), \
 I[28] = (img)(_p3##x,_p1##y,z,v), I[29] = (img)(_p2##x,_p1##y,z,v), I[30] = (img)(_p1##x,_p1##y,z,v), I[31] = (img)(x,_p1##y,z,v), \
 I[32] = (img)(_n1##x,_p1##y,z,v), I[33] = (img)(_n2##x,_p1##y,z,v), I[34] = (img)(_n3##x,_p1##y,z,v), I[35] = (img)(_n4##x,_p1##y,z,v), \
 I[36] = (img)(_p4##x,y,z,v), I[37] = (img)(_p3##x,y,z,v), I[38] = (img)(_p2##x,y,z,v), I[39] = (img)(_p1##x,y,z,v), \
 I[40] = (img)(x,y,z,v), I[41] = (img)(_n1##x,y,z,v), I[42] = (img)(_n2##x,y,z,v), I[43] = (img)(_n3##x,y,z,v), \
 I[44] = (img)(_n4##x,y,z,v), I[45] = (img)(_p4##x,_n1##y,z,v), I[46] = (img)(_p3##x,_n1##y,z,v), I[47] = (img)(_p2##x,_n1##y,z,v), \
 I[48] = (img)(_p1##x,_n1##y,z,v), I[49] = (img)(x,_n1##y,z,v), I[50] = (img)(_n1##x,_n1##y,z,v), I[51] = (img)(_n2##x,_n1##y,z,v), \
 I[52] = (img)(_n3##x,_n1##y,z,v), I[53] = (img)(_n4##x,_n1##y,z,v), I[54] = (img)(_p4##x,_n2##y,z,v), I[55] = (img)(_p3##x,_n2##y,z,v), \
 I[56] = (img)(_p2##x,_n2##y,z,v), I[57] = (img)(_p1##x,_n2##y,z,v), I[58] = (img)(x,_n2##y,z,v), I[59] = (img)(_n1##x,_n2##y,z,v), \
 I[60] = (img)(_n2##x,_n2##y,z,v), I[61] = (img)(_n3##x,_n2##y,z,v), I[62] = (img)(_n4##x,_n2##y,z,v), I[63] = (img)(_p4##x,_n3##y,z,v), \
 I[64] = (img)(_p3##x,_n3##y,z,v), I[65] = (img)(_p2##x,_n3##y,z,v), I[66] = (img)(_p1##x,_n3##y,z,v), I[67] = (img)(x,_n3##y,z,v), \
 I[68] = (img)(_n1##x,_n3##y,z,v), I[69] = (img)(_n2##x,_n3##y,z,v), I[70] = (img)(_n3##x,_n3##y,z,v), I[71] = (img)(_n4##x,_n3##y,z,v), \
 I[72] = (img)(_p4##x,_n4##y,z,v), I[73] = (img)(_p3##x,_n4##y,z,v), I[74] = (img)(_p2##x,_n4##y,z,v), I[75] = (img)(_p1##x,_n4##y,z,v), \
 I[76] = (img)(x,_n4##y,z,v), I[77] = (img)(_n1##x,_n4##y,z,v), I[78] = (img)(_n2##x,_n4##y,z,v), I[79] = (img)(_n3##x,_n4##y,z,v), \
 I[80] = (img)(_n4##x,_n4##y,z,v)

#define cimg_get2x2x2(img,x,y,z,v,I) \
  I[0] = (img)(x,y,z,v), I[1] = (img)(_n1##x,y,z,v), I[2] = (img)(x,_n1##y,z,v), I[3] = (img)(_n1##x,_n1##y,z,v), \
  I[4] = (img)(x,y,_n1##z,v), I[5] = (img)(_n1##x,y,_n1##z,v), I[6] = (img)(x,_n1##y,_n1##z,v), I[7] = (img)(_n1##x,_n1##y,_n1##z,v)

#define cimg_get3x3x3(img,x,y,z,v,I) \
  I[0] = (img)(_p1##x,_p1##y,_p1##z,v), I[1] = (img)(x,_p1##y,_p1##z,v), I[2] = (img)(_n1##x,_p1##y,_p1##z,v), \
  I[3] = (img)(_p1##x,y,_p1##z,v), I[4] = (img)(x,y,_p1##z,v), I[5] = (img)(_n1##x,y,_p1##z,v), \
  I[6] = (img)(_p1##x,_n1##y,_p1##z,v), I[7] = (img)(x,_n1##y,_p1##z,v), I[8] = (img)(_n1##x,_n1##y,_p1##z,v), \
  I[9] = (img)(_p1##x,_p1##y,z,v), I[10] = (img)(x,_p1##y,z,v), I[11] = (img)(_n1##x,_p1##y,z,v), \
  I[12] = (img)(_p1##x,y,z,v), I[13] = (img)(x,y,z,v), I[14] = (img)(_n1##x,y,z,v), \
  I[15] = (img)(_p1##x,_n1##y,z,v), I[16] = (img)(x,_n1##y,z,v), I[17] = (img)(_n1##x,_n1##y,z,v), \
  I[18] = (img)(_p1##x,_p1##y,_n1##z,v), I[19] = (img)(x,_p1##y,_n1##z,v), I[20] = (img)(_n1##x,_p1##y,_n1##z,v), \
  I[21] = (img)(_p1##x,y,_n1##z,v), I[22] = (img)(x,y,_n1##z,v), I[23] = (img)(_n1##x,y,_n1##z,v), \
  I[24] = (img)(_p1##x,_n1##y,_n1##z,v), I[25] = (img)(x,_n1##y,_n1##z,v), I[26] = (img)(_n1##x,_n1##y,_n1##z,v)

// Define various image loops.
//
// These macros generally avoid the use of iterators, but you are not forced to used them !
//
#define cimg_for(img,ptr,T_ptr) for (T_ptr *ptr = (img).data + (img).size(); (ptr--)>(img).data; )
#define cimg_foroff(img,off) for (unsigned int off = 0, _max##off = (unsigned int)(img).size(); off<_max##off; ++off)
#define cimglist_for(list,l) for (unsigned int l=0; l<(list).size; ++l)
#define cimglist_apply(list,fn) cimglist_for(list,__##fn) (list)[__##fn].fn

#define cimg_for1(bound,i) for (int i = 0; i<(int)(bound); ++i)
#define cimg_forX(img,x) cimg_for1((img).width,x)
#define cimg_forY(img,y) cimg_for1((img).height,y)
#define cimg_forZ(img,z) cimg_for1((img).depth,z)
#define cimg_forV(img,v) cimg_for1((img).dim,v)
#define cimg_forXY(img,x,y) cimg_forY(img,y) cimg_forX(img,x)
#define cimg_forXZ(img,x,z) cimg_forZ(img,z) cimg_forX(img,x)
#define cimg_forYZ(img,y,z) cimg_forZ(img,z) cimg_forY(img,y)
#define cimg_forXV(img,x,v) cimg_forV(img,v) cimg_forX(img,x)
#define cimg_forYV(img,y,v) cimg_forV(img,v) cimg_forY(img,y)
#define cimg_forZV(img,z,v) cimg_forV(img,v) cimg_forZ(img,z)
#define cimg_forXYZ(img,x,y,z) cimg_forZ(img,z) cimg_forXY(img,x,y)
#define cimg_forXYV(img,x,y,v) cimg_forV(img,v) cimg_forXY(img,x,y)
#define cimg_forXZV(img,x,z,v) cimg_forV(img,v) cimg_forXZ(img,x,z)
#define cimg_forYZV(img,y,z,v) cimg_forV(img,v) cimg_forYZ(img,y,z)
#define cimg_forXYZV(img,x,y,z,v) cimg_forV(img,v) cimg_forXYZ(img,x,y,z)

#define cimg_for_in1(bound,i0,i1,i) \
 for (int i = (int)(i0)<0?0:(int)(i0), _max##i = (int)(i1)<(int)(bound)?(int)(i1):(int)(bound)-1; i<=_max##i; ++i)
#define cimg_for_inX(img,x0,x1,x) cimg_for_in1((img).width,x0,x1,x)
#define cimg_for_inY(img,y0,y1,y) cimg_for_in1((img).height,y0,y1,y)
#define cimg_for_inZ(img,z0,z1,z) cimg_for_in1((img).depth,z0,z1,z)
#define cimg_for_inV(img,v0,v1,v) cimg_for_in1((img).dim,v0,v1,v)
#define cimg_for_inXY(img,x0,y0,x1,y1,x,y) cimg_for_inY(img,y0,y1,y) cimg_for_inX(img,x0,x1,x)
#define cimg_for_inXZ(img,x0,z0,x1,z1,x,z) cimg_for_inZ(img,z0,z1,z) cimg_for_inX(img,x0,x1,x)
#define cimg_for_inXV(img,x0,v0,x1,v1,x,v) cimg_for_inV(img,v0,v1,v) cimg_for_inX(img,x0,x1,x)
#define cimg_for_inYZ(img,y0,z0,y1,z1,y,z) cimg_for_inZ(img,x0,z1,z) cimg_for_inY(img,y0,y1,y)
#define cimg_for_inYV(img,y0,v0,y1,v1,y,v) cimg_for_inV(img,v0,v1,v) cimg_for_inY(img,y0,y1,y)
#define cimg_for_inZV(img,z0,v0,z1,v1,z,v) cimg_for_inV(img,v0,v1,v) cimg_for_inZ(img,z0,z1,z)
#define cimg_for_inXYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_inZ(img,z0,z1,z) cimg_for_inXY(img,x0,y0,x1,y1,x,y)
#define cimg_for_inXYV(img,x0,y0,v0,x1,y1,v1,x,y,v) cimg_for_inV(img,v0,v1,v) cimg_for_inXY(img,x0,y0,x1,y1,x,y)
#define cimg_for_inXZV(img,x0,z0,v0,x1,z1,v1,x,z,v) cimg_for_inV(img,v0,v1,v) cimg_for_inXZ(img,x0,z0,x1,z1,x,z)
#define cimg_for_inYZV(img,y0,z0,v0,y1,z1,v1,y,z,v) cimg_for_inV(img,v0,v1,v) cimg_for_inYZ(img,y0,z0,y1,z1,y,z)
#define cimg_for_inXYZV(img,x0,y0,z0,v0,x1,y1,z1,v1,x,y,z,v) cimg_for_inV(img,v0,v1,v) cimg_for_inXYZ(img,x0,y0,z0,x1,y1,z1,x,y,z)
#define cimg_for_insideX(img,x,n) cimg_for_inX(img,n,(img).width-1-(n),x)
#define cimg_for_insideY(img,y,n) cimg_for_inY(img,n,(img).height-1-(n),y)
#define cimg_for_insideZ(img,z,n) cimg_for_inZ(img,n,(img).depth-1-(n),z)
#define cimg_for_insideV(img,v,n) cimg_for_inV(img,n,(img).dim-1-(n),v)
#define cimg_for_insideXY(img,x,y,n) cimg_for_inXY(img,n,n,(img).width-1-(n),(img).height-1-(n),x,y)
#define cimg_for_insideXYZ(img,x,y,z,n) cimg_for_inXYZ(img,n,n,n,(img).width-1-(n),(img).height-1-(n),(img).depth-1-(n),x,y,z)
#define cimg_for_insideXYZV(img,x,y,z,v,n) cimg_for_inXYZ(img,n,n,n,(img).width-1-(n),(img).height-1-(n),(img).depth-1-(n),x,y,z)

#define cimg_for_out1(boundi,i0,i1,i) \
 for (int i = (int)(i0)>0?0:(int)(i1)+1; i<(int)(boundi); ++i, i = i==(int)(i0)?(int)(i1)+1:i)
#define cimg_for_out2(boundi,boundj,i0,j0,i1,j1,i,j) \
 for (int j = 0; j<(int)(boundj); ++j) \
 for (int _n1j = (int)(j<(int)(j0) || j>(int)(j1)), i = _n1j?0:(int)(i0)>0?0:(int)(i1)+1; i<(int)(boundi); \
  ++i, i = _n1j?i:(i==(int)(i0)?(int)(i1)+1:i))
#define cimg_for_out3(boundi,boundj,boundk,i0,j0,k0,i1,j1,k1,i,j,k) \
 for (int k = 0; k<(int)(boundk); ++k) \
 for (int _n1k = (int)(k<(int)(k0) || k>(int)(k1)), j = 0; j<(int)(boundj); ++j) \
 for (int _n1j = (int)(j<(int)(j0) || j>(int)(j1)), i = _n1j || _n1k?0:(int)(i0)>0?0:(int)(i1)+1; i<(int)(boundi); \
  ++i, i = _n1j || _n1k?i:(i==(int)(i0)?(int)(i1)+1:i))
#define cimg_for_out4(boundi,boundj,boundk,boundl,i0,j0,k0,l0,i1,j1,k1,l1,i,j,k,l) \
 for (int l = 0; l<(int)(boundl); ++l) \
 for (int _n1l = (int)(l<(int)(l0) || l>(int)(l1)), k = 0; k<(int)(boundk); ++k) \
 for (int _n1k = (int)(k<(int)(k0) || k>(int)(k1)), j = 0; j<(int)(boundj); ++j) \
 for (int _n1j = (int)(j<(int)(j0) || j>(int)(j1)), i = _n1j || _n1k || _n1l?0:(int)(i0)>0?0:(int)(i1)+1; i<(int)(boundi); \
  ++i, i = _n1j || _n1k || _n1l?i:(i==(int)(i0)?(int)(i1)+1:i))
#define cimg_for_outX(img,x0,x1,x) cimg_for_out1((img).width,x0,x1,x)
#define cimg_for_outY(img,y0,y1,y) cimg_for_out1((img).height,y0,y1,y)
#define cimg_for_outZ(img,z0,z1,z) cimg_for_out1((img).depth,z0,z1,z)
#define cimg_for_outV(img,v0,v1,v) cimg_for_out1((img).dim,v0,v1,v)
#define cimg_for_outXY(img,x0,y0,x1,y1,x,y) cimg_for_out2((img).width,(img).height,x0,y0,x1,y1,x,y)
#define cimg_for_outXZ(img,x0,z0,x1,z1,x,z) cimg_for_out2((img).width,(img).depth,x0,z0,x1,z1,x,z)
#define cimg_for_outXV(img,x0,v0,x1,v1,x,v) cimg_for_out2((img).width,(img).dim,x0,v0,x1,v1,x,v)
#define cimg_for_outYZ(img,y0,z0,y1,z1,y,z) cimg_for_out2((img).height,(img).depth,y0,z0,y1,z1,y,z)
#define cimg_for_outYV(img,y0,v0,y1,v1,y,v) cimg_for_out2((img).height,(img).dim,y0,v0,y1,v1,y,v)
#define cimg_for_outZV(img,z0,v0,z1,v1,z,v) cimg_for_out2((img).depth,(img).dim,z0,v0,z1,v1,z,v)
#define cimg_for_outXYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_out3((img).width,(img).height,(img).depth,x0,y0,z0,x1,y1,z1,x,y,z)
#define cimg_for_outXYV(img,x0,y0,v0,x1,y1,v1,x,y,v) cimg_for_out3((img).width,(img).height,(img).dim,x0,y0,v0,x1,y1,v1,x,y,v)
#define cimg_for_outXZV(img,x0,z0,v0,x1,z1,v1,x,z,v) cimg_for_out3((img).width,(img).depth,(img).dim,x0,z0,v0,x1,z1,v1,x,z,v)
#define cimg_for_outYZV(img,y0,z0,v0,y1,z1,v1,y,z,v) cimg_for_out3((img).height,(img).depth,(img).dim,y0,z0,v0,y1,z1,v1,y,z,v)
#define cimg_for_outXYZV(img,x0,y0,z0,v0,x1,y1,z1,v1,x,y,z,v) \
 cimg_for_out4((img).width,(img).height,(img).depth,(img).dim,x0,y0,z0,v0,x1,y1,z1,v1,x,y,z,v)
#define cimg_for_borderX(img,x,n) cimg_for_outX(img,n,(img).width-1-(n),x)
#define cimg_for_borderY(img,y,n) cimg_for_outY(img,n,(img).height-1-(n),y)
#define cimg_for_borderZ(img,z,n) cimg_for_outZ(img,n,(img).depth-1-(n),z)
#define cimg_for_borderV(img,v,n) cimg_for_outV(img,n,(img).dim-1-(n),v)
#define cimg_for_borderXY(img,x,y,n) cimg_for_outXY(img,n,n,(img).width-1-(n),(img).height-1-(n),x,y)
#define cimg_for_borderXYZ(img,x,y,z,n) cimg_for_outXYZ(img,n,n,n,(img).width-1-(n),(img).height-1-(n),(img).depth-1-(n),x,y,z)
#define cimg_for_borderXYZV(img,x,y,z,v,n) \
 cimg_for_outXYZV(img,n,n,n,n,(img).width-1-(n),(img).height-1-(n),(img).depth-1-(n),(img).dim-1-(n),x,y,z,v)

#define cimg_for_spiralXY(img,x,y) \
 for (int x = 0, y = 0, _n1##x = 1, _n1##y = (int)((img).width*(img).height); _n1##y; \
      --_n1##y, _n1##x += (_n1##x>>2)-((!(_n1##x&3)?--y:((_n1##x&3)==1?(img).width-1-++x:((_n1##x&3)==2?(img).height-1-++y:--x))))?0:1)

#define cimg_for_lineXY(x,y,x0,y0,x1,y1) \
 for (int x = (int)(x0), y = (int)(y0), _sx = 1, _sy = 1, _steep = 0, \
      _dx=(x1)>(x0)?(int)(x1)-(int)(x0):(_sx=-1,(int)(x0)-(int)(x1)), \
      _dy=(y1)>(y0)?(int)(y1)-(int)(y0):(_sy=-1,(int)(y0)-(int)(y1)), \
      _counter = _dx, \
      _err = _dx>_dy?(_dy>>1):((_steep=1),(_counter=_dy),(_dx>>1)); \
      _counter>=0; \
      --_counter, x+=_steep? \
      (y+=_sy,(_err-=_dx)<0?_err+=_dy,_sx:0): \
      (y+=(_err-=_dy)<0?_err+=_dx,_sy:0,_sx))

#define cimg_for2(bound,i) \
 for (int i = 0, _n1##i = 1>=(bound)?(int)(bound)-1:1; \
      _n1##i<(int)(bound) || i==--_n1##i; \
      ++i, ++_n1##i)
#define cimg_for2X(img,x) cimg_for2((img).width,x)
#define cimg_for2Y(img,y) cimg_for2((img).height,y)
#define cimg_for2Z(img,z) cimg_for2((img).depth,z)
#define cimg_for2V(img,v) cimg_for2((img).dim,v)
#define cimg_for2XY(img,x,y) cimg_for2Y(img,y) cimg_for2X(img,x)
#define cimg_for2XZ(img,x,z) cimg_for2Z(img,z) cimg_for2X(img,x)
#define cimg_for2XV(img,x,v) cimg_for2V(img,v) cimg_for2X(img,x)
#define cimg_for2YZ(img,y,z) cimg_for2Z(img,z) cimg_for2Y(img,y)
#define cimg_for2YV(img,y,v) cimg_for2V(img,v) cimg_for2Y(img,y)
#define cimg_for2ZV(img,z,v) cimg_for2V(img,v) cimg_for2Z(img,z)
#define cimg_for2XYZ(img,x,y,z) cimg_for2Z(img,z) cimg_for2XY(img,x,y)
#define cimg_for2XZV(img,x,z,v) cimg_for2V(img,v) cimg_for2XZ(img,x,z)
#define cimg_for2YZV(img,y,z,v) cimg_for2V(img,v) cimg_for2YZ(img,y,z)
#define cimg_for2XYZV(img,x,y,z,v) cimg_for2V(img,v) cimg_for2XYZ(img,x,y,z)

#define cimg_for_in2(bound,i0,i1,i) \
 for (int i = (int)(i0)<0?0:(int)(i0), \
      _n1##i = i+1>=(int)(bound)?(int)(bound)-1:i+1; \
      i<=(int)(i1) && (_n1##i<(int)(bound) || i==--_n1##i); \
      ++i, ++_n1##i)
#define cimg_for_in2X(img,x0,x1,x) cimg_for_in2((img).width,x0,x1,x)
#define cimg_for_in2Y(img,y0,y1,y) cimg_for_in2((img).height,y0,y1,y)
#define cimg_for_in2Z(img,z0,z1,z) cimg_for_in2((img).depth,z0,z1,z)
#define cimg_for_in2V(img,v0,v1,v) cimg_for_in2((img).dim,v0,v1,v)
#define cimg_for_in2XY(img,x0,y0,x1,y1,x,y) cimg_for_in2Y(img,y0,y1,y) cimg_for_in2X(img,x0,x1,x)
#define cimg_for_in2XZ(img,x0,z0,x1,z1,x,z) cimg_for_in2Z(img,z0,z1,z) cimg_for_in2X(img,x0,x1,x)
#define cimg_for_in2XV(img,x0,v0,x1,v1,x,v) cimg_for_in2V(img,v0,v1,v) cimg_for_in2X(img,x0,x1,x)
#define cimg_for_in2YZ(img,y0,z0,y1,z1,y,z) cimg_for_in2Z(img,z0,z1,z) cimg_for_in2Y(img,y0,y1,y)
#define cimg_for_in2YV(img,y0,v0,y1,v1,y,v) cimg_for_in2V(img,v0,v1,v) cimg_for_in2Y(img,y0,y1,y)
#define cimg_for_in2ZV(img,z0,v0,z1,v1,z,v) cimg_for_in2V(img,v0,v1,v) cimg_for_in2Z(img,z0,z1,z)
#define cimg_for_in2XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_in2Z(img,z0,z1,z) cimg_for_in2XY(img,x0,y0,x1,y1,x,y)
#define cimg_for_in2XZV(img,x0,z0,v0,x1,y1,v1,x,z,v) cimg_for_in2V(img,v0,v1,v) cimg_for_in2XZ(img,x0,y0,x1,y1,x,z)
#define cimg_for_in2YZV(img,y0,z0,v0,y1,z1,v1,y,z,v) cimg_for_in2V(img,v0,v1,v) cimg_for_in2YZ(img,y0,z0,y1,z1,y,z)
#define cimg_for_in2XYZV(img,x0,y0,z0,v0,x1,y1,z1,v1,x,y,z,v) cimg_for_in2V(img,v0,v1,v) cimg_for_in2XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z)

#define cimg_for3(bound,i) \
 for (int i = 0, _p1##i = 0, \
      _n1##i = 1>=(bound)?(int)(bound)-1:1; \
      _n1##i<(int)(bound) || i==--_n1##i; \
      _p1##i = i++, ++_n1##i)
#define cimg_for3X(img,x) cimg_for3((img).width,x)
#define cimg_for3Y(img,y) cimg_for3((img).height,y)
#define cimg_for3Z(img,z) cimg_for3((img).depth,z)
#define cimg_for3V(img,v) cimg_for3((img).dim,v)
#define cimg_for3XY(img,x,y) cimg_for3Y(img,y) cimg_for3X(img,x)
#define cimg_for3XZ(img,x,z) cimg_for3Z(img,z) cimg_for3X(img,x)
#define cimg_for3XV(img,x,v) cimg_for3V(img,v) cimg_for3X(img,x)
#define cimg_for3YZ(img,y,z) cimg_for3Z(img,z) cimg_for3Y(img,y)
#define cimg_for3YV(img,y,v) cimg_for3V(img,v) cimg_for3Y(img,y)
#define cimg_for3ZV(img,z,v) cimg_for3V(img,v) cimg_for3Z(img,z)
#define cimg_for3XYZ(img,x,y,z) cimg_for3Z(img,z) cimg_for3XY(img,x,y)
#define cimg_for3XZV(img,x,z,v) cimg_for3V(img,v) cimg_for3XZ(img,x,z)
#define cimg_for3YZV(img,y,z,v) cimg_for3V(img,v) cimg_for3YZ(img,y,z)
#define cimg_for3XYZV(img,x,y,z,v) cimg_for3V(img,v) cimg_for3XYZ(img,x,y,z)

#define cimg_for_in3(bound,i0,i1,i) \
 for (int i = (int)(i0)<0?0:(int)(i0), \
      _p1##i = i-1<0?0:i-1, \
      _n1##i = i+1>=(int)(bound)?(int)(bound)-1:i+1; \
      i<=(int)(i1) && (_n1##i<(int)(bound) || i==--_n1##i); \
      _p1##i = i++, ++_n1##i)
#define cimg_for_in3X(img,x0,x1,x) cimg_for_in3((img).width,x0,x1,x)
#define cimg_for_in3Y(img,y0,y1,y) cimg_for_in3((img).height,y0,y1,y)
#define cimg_for_in3Z(img,z0,z1,z) cimg_for_in3((img).depth,z0,z1,z)
#define cimg_for_in3V(img,v0,v1,v) cimg_for_in3((img).dim,v0,v1,v)
#define cimg_for_in3XY(img,x0,y0,x1,y1,x,y) cimg_for_in3Y(img,y0,y1,y) cimg_for_in3X(img,x0,x1,x)
#define cimg_for_in3XZ(img,x0,z0,x1,z1,x,z) cimg_for_in3Z(img,z0,z1,z) cimg_for_in3X(img,x0,x1,x)
#define cimg_for_in3XV(img,x0,v0,x1,v1,x,v) cimg_for_in3V(img,v0,v1,v) cimg_for_in3X(img,x0,x1,x)
#define cimg_for_in3YZ(img,y0,z0,y1,z1,y,z) cimg_for_in3Z(img,z0,z1,z) cimg_for_in3Y(img,y0,y1,y)
#define cimg_for_in3YV(img,y0,v0,y1,v1,y,v) cimg_for_in3V(img,v0,v1,v) cimg_for_in3Y(img,y0,y1,y)
#define cimg_for_in3ZV(img,z0,v0,z1,v1,z,v) cimg_for_in3V(img,v0,v1,v) cimg_for_in3Z(img,z0,z1,z)
#define cimg_for_in3XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_in3Z(img,z0,z1,z) cimg_for_in3XY(img,x0,y0,x1,y1,x,y)
#define cimg_for_in3XZV(img,x0,z0,v0,x1,y1,v1,x,z,v) cimg_for_in3V(img,v0,v1,v) cimg_for_in3XZ(img,x0,y0,x1,y1,x,z)
#define cimg_for_in3YZV(img,y0,z0,v0,y1,z1,v1,y,z,v) cimg_for_in3V(img,v0,v1,v) cimg_for_in3YZ(img,y0,z0,y1,z1,y,z)
#define cimg_for_in3XYZV(img,x0,y0,z0,v0,x1,y1,z1,v1,x,y,z,v) cimg_for_in3V(img,v0,v1,v) cimg_for_in3XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z)

#define cimg_for4(bound,i) \
 for (int i = 0, _p1##i = 0, _n1##i = 1>=(bound)?(int)(bound)-1:1, \
      _n2##i = 2>=(bound)?(int)(bound)-1:2; \
      _n2##i<(int)(bound) || _n1##i==--_n2##i || i==(_n2##i = --_n1##i); \
      _p1##i = i++, ++_n1##i, ++_n2##i)
#define cimg_for4X(img,x) cimg_for4((img).width,x)
#define cimg_for4Y(img,y) cimg_for4((img).height,y)
#define cimg_for4Z(img,z) cimg_for4((img).depth,z)
#define cimg_for4V(img,v) cimg_for4((img).dim,v)
#define cimg_for4XY(img,x,y) cimg_for4Y(img,y) cimg_for4X(img,x)
#define cimg_for4XZ(img,x,z) cimg_for4Z(img,z) cimg_for4X(img,x)
#define cimg_for4XV(img,x,v) cimg_for4V(img,v) cimg_for4X(img,x)
#define cimg_for4YZ(img,y,z) cimg_for4Z(img,z) cimg_for4Y(img,y)
#define cimg_for4YV(img,y,v) cimg_for4V(img,v) cimg_for4Y(img,y)
#define cimg_for4ZV(img,z,v) cimg_for4V(img,v) cimg_for4Z(img,z)
#define cimg_for4XYZ(img,x,y,z) cimg_for4Z(img,z) cimg_for4XY(img,x,y)
#define cimg_for4XZV(img,x,z,v) cimg_for4V(img,v) cimg_for4XZ(img,x,z)
#define cimg_for4YZV(img,y,z,v) cimg_for4V(img,v) cimg_for4YZ(img,y,z)
#define cimg_for4XYZV(img,x,y,z,v) cimg_for4V(img,v) cimg_for4XYZ(img,x,y,z)

#define cimg_for_in4(bound,i0,i1,i) \
 for (int i = (int)(i0)<0?0:(int)(i0), \
      _p1##i = i-1<0?0:i-1, \
      _n1##i = i+1>=(int)(bound)?(int)(bound)-1:i+1, \
      _n2##i = i+2>=(int)(bound)?(int)(bound)-1:i+2; \
      i<=(int)(i1) && (_n2##i<(int)(bound) || _n1##i==--_n2##i || i==(_n2##i = --_n1##i)); \
      _p1##i = i++, ++_n1##i, ++_n2##i)
#define cimg_for_in4X(img,x0,x1,x) cimg_for_in4((img).width,x0,x1,x)
#define cimg_for_in4Y(img,y0,y1,y) cimg_for_in4((img).height,y0,y1,y)
#define cimg_for_in4Z(img,z0,z1,z) cimg_for_in4((img).depth,z0,z1,z)
#define cimg_for_in4V(img,v0,v1,v) cimg_for_in4((img).dim,v0,v1,v)
#define cimg_for_in4XY(img,x0,y0,x1,y1,x,y) cimg_for_in4Y(img,y0,y1,y) cimg_for_in4X(img,x0,x1,x)
#define cimg_for_in4XZ(img,x0,z0,x1,z1,x,z) cimg_for_in4Z(img,z0,z1,z) cimg_for_in4X(img,x0,x1,x)
#define cimg_for_in4XV(img,x0,v0,x1,v1,x,v) cimg_for_in4V(img,v0,v1,v) cimg_for_in4X(img,x0,x1,x)
#define cimg_for_in4YZ(img,y0,z0,y1,z1,y,z) cimg_for_in4Z(img,z0,z1,z) cimg_for_in4Y(img,y0,y1,y)
#define cimg_for_in4YV(img,y0,v0,y1,v1,y,v) cimg_for_in4V(img,v0,v1,v) cimg_for_in4Y(img,y0,y1,y)
#define cimg_for_in4ZV(img,z0,v0,z1,v1,z,v) cimg_for_in4V(img,v0,v1,v) cimg_for_in4Z(img,z0,z1,z)
#define cimg_for_in4XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_in4Z(img,z0,z1,z) cimg_for_in4XY(img,x0,y0,x1,y1,x,y)
#define cimg_for_in4XZV(img,x0,z0,v0,x1,y1,v1,x,z,v) cimg_for_in4V(img,v0,v1,v) cimg_for_in4XZ(img,x0,y0,x1,y1,x,z)
#define cimg_for_in4YZV(img,y0,z0,v0,y1,z1,v1,y,z,v) cimg_for_in4V(img,v0,v1,v) cimg_for_in4YZ(img,y0,z0,y1,z1,y,z)
#define cimg_for_in4XYZV(img,x0,y0,z0,v0,x1,y1,z1,v1,x,y,z,v) cimg_for_in4V(img,v0,v1,v) cimg_for_in4XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z)

#define cimg_for5(bound,i) \
 for (int i = 0, _p2##i = 0, _p1##i = 0, \
      _n1##i = 1>=(bound)?(int)(bound)-1:1, \
      _n2##i = 2>=(bound)?(int)(bound)-1:2; \
      _n2##i<(int)(bound) || _n1##i==--_n2##i || i==(_n2##i = --_n1##i); \
      _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i)
#define cimg_for5X(img,x) cimg_for5((img).width,x)
#define cimg_for5Y(img,y) cimg_for5((img).height,y)
#define cimg_for5Z(img,z) cimg_for5((img).depth,z)
#define cimg_for5V(img,v) cimg_for5((img).dim,v)
#define cimg_for5XY(img,x,y) cimg_for5Y(img,y) cimg_for5X(img,x)
#define cimg_for5XZ(img,x,z) cimg_for5Z(img,z) cimg_for5X(img,x)
#define cimg_for5XV(img,x,v) cimg_for5V(img,v) cimg_for5X(img,x)
#define cimg_for5YZ(img,y,z) cimg_for5Z(img,z) cimg_for5Y(img,y)
#define cimg_for5YV(img,y,v) cimg_for5V(img,v) cimg_for5Y(img,y)
#define cimg_for5ZV(img,z,v) cimg_for5V(img,v) cimg_for5Z(img,z)
#define cimg_for5XYZ(img,x,y,z) cimg_for5Z(img,z) cimg_for5XY(img,x,y)
#define cimg_for5XZV(img,x,z,v) cimg_for5V(img,v) cimg_for5XZ(img,x,z)
#define cimg_for5YZV(img,y,z,v) cimg_for5V(img,v) cimg_for5YZ(img,y,z)
#define cimg_for5XYZV(img,x,y,z,v) cimg_for5V(img,v) cimg_for5XYZ(img,x,y,z)

#define cimg_for_in5(bound,i0,i1,i) \
 for (int i = (int)(i0)<0?0:(int)(i0), \
      _p2##i = i-2<0?0:i-2, \
      _p1##i = i-1<0?0:i-1, \
      _n1##i = i+1>=(int)(bound)?(int)(bound)-1:i+1, \
      _n2##i = i+2>=(int)(bound)?(int)(bound)-1:i+2; \
      i<=(int)(i1) && (_n2##i<(int)(bound) || _n1##i==--_n2##i || i==(_n2##i = --_n1##i)); \
      _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i)
#define cimg_for_in5X(img,x0,x1,x) cimg_for_in5((img).width,x0,x1,x)
#define cimg_for_in5Y(img,y0,y1,y) cimg_for_in5((img).height,y0,y1,y)
#define cimg_for_in5Z(img,z0,z1,z) cimg_for_in5((img).depth,z0,z1,z)
#define cimg_for_in5V(img,v0,v1,v) cimg_for_in5((img).dim,v0,v1,v)
#define cimg_for_in5XY(img,x0,y0,x1,y1,x,y) cimg_for_in5Y(img,y0,y1,y) cimg_for_in5X(img,x0,x1,x)
#define cimg_for_in5XZ(img,x0,z0,x1,z1,x,z) cimg_for_in5Z(img,z0,z1,z) cimg_for_in5X(img,x0,x1,x)
#define cimg_for_in5XV(img,x0,v0,x1,v1,x,v) cimg_for_in5V(img,v0,v1,v) cimg_for_in5X(img,x0,x1,x)
#define cimg_for_in5YZ(img,y0,z0,y1,z1,y,z) cimg_for_in5Z(img,z0,z1,z) cimg_for_in5Y(img,y0,y1,y)
#define cimg_for_in5YV(img,y0,v0,y1,v1,y,v) cimg_for_in5V(img,v0,v1,v) cimg_for_in5Y(img,y0,y1,y)
#define cimg_for_in5ZV(img,z0,v0,z1,v1,z,v) cimg_for_in5V(img,v0,v1,v) cimg_for_in5Z(img,z0,z1,z)
#define cimg_for_in5XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_in5Z(img,z0,z1,z) cimg_for_in5XY(img,x0,y0,x1,y1,x,y)
#define cimg_for_in5XZV(img,x0,z0,v0,x1,y1,v1,x,z,v) cimg_for_in5V(img,v0,v1,v) cimg_for_in5XZ(img,x0,y0,x1,y1,x,z)
#define cimg_for_in5YZV(img,y0,z0,v0,y1,z1,v1,y,z,v) cimg_for_in5V(img,v0,v1,v) cimg_for_in5YZ(img,y0,z0,y1,z1,y,z)
#define cimg_for_in5XYZV(img,x0,y0,z0,v0,x1,y1,z1,v1,x,y,z,v) cimg_for_in5V(img,v0,v1,v) cimg_for_in5XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z)

#define cimg_for6(bound,i) \
 for (int i = 0, _p2##i = 0, _p1##i = 0, \
      _n1##i = 1>=(bound)?(int)(bound)-1:1, \
      _n2##i = 2>=(bound)?(int)(bound)-1:2, \
      _n3##i = 3>=(bound)?(int)(bound)-1:3; \
      _n3##i<(int)(bound) || _n2##i==--_n3##i || _n1##i==--_n2##i || i==(_n3##i = _n2##i = --_n1##i); \
      _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i, ++_n3##i)
#define cimg_for6X(img,x) cimg_for6((img).width,x)
#define cimg_for6Y(img,y) cimg_for6((img).height,y)
#define cimg_for6Z(img,z) cimg_for6((img).depth,z)
#define cimg_for6V(img,v) cimg_for6((img).dim,v)
#define cimg_for6XY(img,x,y) cimg_for6Y(img,y) cimg_for6X(img,x)
#define cimg_for6XZ(img,x,z) cimg_for6Z(img,z) cimg_for6X(img,x)
#define cimg_for6XV(img,x,v) cimg_for6V(img,v) cimg_for6X(img,x)
#define cimg_for6YZ(img,y,z) cimg_for6Z(img,z) cimg_for6Y(img,y)
#define cimg_for6YV(img,y,v) cimg_for6V(img,v) cimg_for6Y(img,y)
#define cimg_for6ZV(img,z,v) cimg_for6V(img,v) cimg_for6Z(img,z)
#define cimg_for6XYZ(img,x,y,z) cimg_for6Z(img,z) cimg_for6XY(img,x,y)
#define cimg_for6XZV(img,x,z,v) cimg_for6V(img,v) cimg_for6XZ(img,x,z)
#define cimg_for6YZV(img,y,z,v) cimg_for6V(img,v) cimg_for6YZ(img,y,z)
#define cimg_for6XYZV(img,x,y,z,v) cimg_for6V(img,v) cimg_for6XYZ(img,x,y,z)

#define cimg_for_in6(bound,i0,i1,i) \
 for (int i = (int)(i0)<0?0:(int)(i0), \
      _p2##i = i-2<0?0:i-2, \
      _p1##i = i-1<0?0:i-1, \
      _n1##i = i+1>=(int)(bound)?(int)(bound)-1:i+1, \
      _n2##i = i+2>=(int)(bound)?(int)(bound)-1:i+2, \
      _n3##i = i+3>=(int)(bound)?(int)(bound)-1:i+3; \
      i<=(int)(i1) && (_n3##i<(int)(bound) || _n2##i==--_n3##i || _n1##i==--_n2##i || i==(_n3##i = _n2##i = --_n1##i)); \
      _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i, ++_n3##i)
#define cimg_for_in6X(img,x0,x1,x) cimg_for_in6((img).width,x0,x1,x)
#define cimg_for_in6Y(img,y0,y1,y) cimg_for_in6((img).height,y0,y1,y)
#define cimg_for_in6Z(img,z0,z1,z) cimg_for_in6((img).depth,z0,z1,z)
#define cimg_for_in6V(img,v0,v1,v) cimg_for_in6((img).dim,v0,v1,v)
#define cimg_for_in6XY(img,x0,y0,x1,y1,x,y) cimg_for_in6Y(img,y0,y1,y) cimg_for_in6X(img,x0,x1,x)
#define cimg_for_in6XZ(img,x0,z0,x1,z1,x,z) cimg_for_in6Z(img,z0,z1,z) cimg_for_in6X(img,x0,x1,x)
#define cimg_for_in6XV(img,x0,v0,x1,v1,x,v) cimg_for_in6V(img,v0,v1,v) cimg_for_in6X(img,x0,x1,x)
#define cimg_for_in6YZ(img,y0,z0,y1,z1,y,z) cimg_for_in6Z(img,z0,z1,z) cimg_for_in6Y(img,y0,y1,y)
#define cimg_for_in6YV(img,y0,v0,y1,v1,y,v) cimg_for_in6V(img,v0,v1,v) cimg_for_in6Y(img,y0,y1,y)
#define cimg_for_in6ZV(img,z0,v0,z1,v1,z,v) cimg_for_in6V(img,v0,v1,v) cimg_for_in6Z(img,z0,z1,z)
#define cimg_for_in6XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_in6Z(img,z0,z1,z) cimg_for_in6XY(img,x0,y0,x1,y1,x,y)
#define cimg_for_in6XZV(img,x0,z0,v0,x1,y1,v1,x,z,v) cimg_for_in6V(img,v0,v1,v) cimg_for_in6XZ(img,x0,y0,x1,y1,x,z)
#define cimg_for_in6YZV(img,y0,z0,v0,y1,z1,v1,y,z,v) cimg_for_in6V(img,v0,v1,v) cimg_for_in6YZ(img,y0,z0,y1,z1,y,z)
#define cimg_for_in6XYZV(img,x0,y0,z0,v0,x1,y1,z1,v1,x,y,z,v) cimg_for_in6V(img,v0,v1,v) cimg_for_in6XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z)

#define cimg_for7(bound,i) \
 for (int i = 0, _p3##i = 0, _p2##i = 0, _p1##i = 0, \
      _n1##i = 1>=(bound)?(int)(bound)-1:1, \
      _n2##i = 2>=(bound)?(int)(bound)-1:2, \
      _n3##i = 3>=(bound)?(int)(bound)-1:3; \
      _n3##i<(int)(bound) || _n2##i==--_n3##i || _n1##i==--_n2##i || i==(_n3##i = _n2##i = --_n1##i); \
      _p3##i = _p2##i, _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i, ++_n3##i)
#define cimg_for7X(img,x) cimg_for7((img).width,x)
#define cimg_for7Y(img,y) cimg_for7((img).height,y)
#define cimg_for7Z(img,z) cimg_for7((img).depth,z)
#define cimg_for7V(img,v) cimg_for7((img).dim,v)
#define cimg_for7XY(img,x,y) cimg_for7Y(img,y) cimg_for7X(img,x)
#define cimg_for7XZ(img,x,z) cimg_for7Z(img,z) cimg_for7X(img,x)
#define cimg_for7XV(img,x,v) cimg_for7V(img,v) cimg_for7X(img,x)
#define cimg_for7YZ(img,y,z) cimg_for7Z(img,z) cimg_for7Y(img,y)
#define cimg_for7YV(img,y,v) cimg_for7V(img,v) cimg_for7Y(img,y)
#define cimg_for7ZV(img,z,v) cimg_for7V(img,v) cimg_for7Z(img,z)
#define cimg_for7XYZ(img,x,y,z) cimg_for7Z(img,z) cimg_for7XY(img,x,y)
#define cimg_for7XZV(img,x,z,v) cimg_for7V(img,v) cimg_for7XZ(img,x,z)
#define cimg_for7YZV(img,y,z,v) cimg_for7V(img,v) cimg_for7YZ(img,y,z)
#define cimg_for7XYZV(img,x,y,z,v) cimg_for7V(img,v) cimg_for7XYZ(img,x,y,z)

#define cimg_for_in7(bound,i0,i1,i) \
 for (int i = (int)(i0)<0?0:(int)(i0), \
      _p3##i = i-3<0?0:i-3, \
      _p2##i = i-2<0?0:i-2, \
      _p1##i = i-1<0?0:i-1, \
      _n1##i = i+1>=(int)(bound)?(int)(bound)-1:i+1, \
      _n2##i = i+2>=(int)(bound)?(int)(bound)-1:i+2, \
      _n3##i = i+3>=(int)(bound)?(int)(bound)-1:i+3; \
      i<=(int)(i1) && (_n3##i<(int)(bound) || _n2##i==--_n3##i || _n1##i==--_n2##i || i==(_n3##i = _n2##i = --_n1##i)); \
      _p3##i = _p2##i, _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i, ++_n3##i)
#define cimg_for_in7X(img,x0,x1,x) cimg_for_in7((img).width,x0,x1,x)
#define cimg_for_in7Y(img,y0,y1,y) cimg_for_in7((img).height,y0,y1,y)
#define cimg_for_in7Z(img,z0,z1,z) cimg_for_in7((img).depth,z0,z1,z)
#define cimg_for_in7V(img,v0,v1,v) cimg_for_in7((img).dim,v0,v1,v)
#define cimg_for_in7XY(img,x0,y0,x1,y1,x,y) cimg_for_in7Y(img,y0,y1,y) cimg_for_in7X(img,x0,x1,x)
#define cimg_for_in7XZ(img,x0,z0,x1,z1,x,z) cimg_for_in7Z(img,z0,z1,z) cimg_for_in7X(img,x0,x1,x)
#define cimg_for_in7XV(img,x0,v0,x1,v1,x,v) cimg_for_in7V(img,v0,v1,v) cimg_for_in7X(img,x0,x1,x)
#define cimg_for_in7YZ(img,y0,z0,y1,z1,y,z) cimg_for_in7Z(img,z0,z1,z) cimg_for_in7Y(img,y0,y1,y)
#define cimg_for_in7YV(img,y0,v0,y1,v1,y,v) cimg_for_in7V(img,v0,v1,v) cimg_for_in7Y(img,y0,y1,y)
#define cimg_for_in7ZV(img,z0,v0,z1,v1,z,v) cimg_for_in7V(img,v0,v1,v) cimg_for_in7Z(img,z0,z1,z)
#define cimg_for_in7XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_in7Z(img,z0,z1,z) cimg_for_in7XY(img,x0,y0,x1,y1,x,y)
#define cimg_for_in7XZV(img,x0,z0,v0,x1,y1,v1,x,z,v) cimg_for_in7V(img,v0,v1,v) cimg_for_in7XZ(img,x0,y0,x1,y1,x,z)
#define cimg_for_in7YZV(img,y0,z0,v0,y1,z1,v1,y,z,v) cimg_for_in7V(img,v0,v1,v) cimg_for_in7YZ(img,y0,z0,y1,z1,y,z)
#define cimg_for_in7XYZV(img,x0,y0,z0,v0,x1,y1,z1,v1,x,y,z,v) cimg_for_in7V(img,v0,v1,v) cimg_for_in7XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z)

#define cimg_for8(bound,i) \
 for (int i = 0, _p3##i = 0, _p2##i = 0, _p1##i = 0, \
      _n1##i = 1>=(bound)?(int)(bound)-1:1, \
      _n2##i = 2>=(bound)?(int)(bound)-1:2, \
      _n3##i = 3>=(bound)?(int)(bound)-1:3, \
      _n4##i = 4>=(bound)?(int)(bound)-1:4; \
      _n4##i<(int)(bound) || _n3##i==--_n4##i || _n2##i==--_n3##i || _n1##i==--_n2##i || \
      i==(_n4##i = _n3##i = _n2##i = --_n1##i); \
      _p3##i = _p2##i, _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i, ++_n3##i, ++_n4##i)
#define cimg_for8X(img,x) cimg_for8((img).width,x)
#define cimg_for8Y(img,y) cimg_for8((img).height,y)
#define cimg_for8Z(img,z) cimg_for8((img).depth,z)
#define cimg_for8V(img,v) cimg_for8((img).dim,v)
#define cimg_for8XY(img,x,y) cimg_for8Y(img,y) cimg_for8X(img,x)
#define cimg_for8XZ(img,x,z) cimg_for8Z(img,z) cimg_for8X(img,x)
#define cimg_for8XV(img,x,v) cimg_for8V(img,v) cimg_for8X(img,x)
#define cimg_for8YZ(img,y,z) cimg_for8Z(img,z) cimg_for8Y(img,y)
#define cimg_for8YV(img,y,v) cimg_for8V(img,v) cimg_for8Y(img,y)
#define cimg_for8ZV(img,z,v) cimg_for8V(img,v) cimg_for8Z(img,z)
#define cimg_for8XYZ(img,x,y,z) cimg_for8Z(img,z) cimg_for8XY(img,x,y)
#define cimg_for8XZV(img,x,z,v) cimg_for8V(img,v) cimg_for8XZ(img,x,z)
#define cimg_for8YZV(img,y,z,v) cimg_for8V(img,v) cimg_for8YZ(img,y,z)
#define cimg_for8XYZV(img,x,y,z,v) cimg_for8V(img,v) cimg_for8XYZ(img,x,y,z)

#define cimg_for_in8(bound,i0,i1,i) \
 for (int i = (int)(i0)<0?0:(int)(i0), \
      _p3##i = i-3<0?0:i-3, \
      _p2##i = i-2<0?0:i-2, \
      _p1##i = i-1<0?0:i-1, \
      _n1##i = i+1>=(int)(bound)?(int)(bound)-1:i+1, \
      _n2##i = i+2>=(int)(bound)?(int)(bound)-1:i+2, \
      _n3##i = i+3>=(int)(bound)?(int)(bound)-1:i+3, \
      _n4##i = i+4>=(int)(bound)?(int)(bound)-1:i+4; \
      i<=(int)(i1) && (_n4##i<(int)(bound) || _n3##i==--_n4##i || _n2##i==--_n3##i || _n1##i==--_n2##i || \
      i==(_n4##i = _n3##i = _n2##i = --_n1##i)); \
      _p3##i = _p2##i, _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i, ++_n3##i, ++_n4##i)
#define cimg_for_in8X(img,x0,x1,x) cimg_for_in8((img).width,x0,x1,x)
#define cimg_for_in8Y(img,y0,y1,y) cimg_for_in8((img).height,y0,y1,y)
#define cimg_for_in8Z(img,z0,z1,z) cimg_for_in8((img).depth,z0,z1,z)
#define cimg_for_in8V(img,v0,v1,v) cimg_for_in8((img).dim,v0,v1,v)
#define cimg_for_in8XY(img,x0,y0,x1,y1,x,y) cimg_for_in8Y(img,y0,y1,y) cimg_for_in8X(img,x0,x1,x)
#define cimg_for_in8XZ(img,x0,z0,x1,z1,x,z) cimg_for_in8Z(img,z0,z1,z) cimg_for_in8X(img,x0,x1,x)
#define cimg_for_in8XV(img,x0,v0,x1,v1,x,v) cimg_for_in8V(img,v0,v1,v) cimg_for_in8X(img,x0,x1,x)
#define cimg_for_in8YZ(img,y0,z0,y1,z1,y,z) cimg_for_in8Z(img,z0,z1,z) cimg_for_in8Y(img,y0,y1,y)
#define cimg_for_in8YV(img,y0,v0,y1,v1,y,v) cimg_for_in8V(img,v0,v1,v) cimg_for_in8Y(img,y0,y1,y)
#define cimg_for_in8ZV(img,z0,v0,z1,v1,z,v) cimg_for_in8V(img,v0,v1,v) cimg_for_in8Z(img,z0,z1,z)
#define cimg_for_in8XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_in8Z(img,z0,z1,z) cimg_for_in8XY(img,x0,y0,x1,y1,x,y)
#define cimg_for_in8XZV(img,x0,z0,v0,x1,y1,v1,x,z,v) cimg_for_in8V(img,v0,v1,v) cimg_for_in8XZ(img,x0,y0,x1,y1,x,z)
#define cimg_for_in8YZV(img,y0,z0,v0,y1,z1,v1,y,z,v) cimg_for_in8V(img,v0,v1,v) cimg_for_in8YZ(img,y0,z0,y1,z1,y,z)
#define cimg_for_in8XYZV(img,x0,y0,z0,v0,x1,y1,z1,v1,x,y,z,v) cimg_for_in8V(img,v0,v1,v) cimg_for_in8XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z)

#define cimg_for9(bound,i) \
  for (int i = 0, _p4##i = 0, _p3##i = 0, _p2##i = 0, _p1##i = 0, \
       _n1##i = 1>=(int)(bound)?(int)(bound)-1:1, \
       _n2##i = 2>=(int)(bound)?(int)(bound)-1:2, \
       _n3##i = 3>=(int)(bound)?(int)(bound)-1:3, \
       _n4##i = 4>=(int)(bound)?(int)(bound)-1:4; \
       _n4##i<(int)(bound) || _n3##i==--_n4##i || _n2##i==--_n3##i || _n1##i==--_n2##i || \
       i==(_n4##i = _n3##i = _n2##i = --_n1##i); \
       _p4##i = _p3##i, _p3##i = _p2##i, _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i, ++_n3##i, ++_n4##i)
#define cimg_for9X(img,x) cimg_for9((img).width,x)
#define cimg_for9Y(img,y) cimg_for9((img).height,y)
#define cimg_for9Z(img,z) cimg_for9((img).depth,z)
#define cimg_for9V(img,v) cimg_for9((img).dim,v)
#define cimg_for9XY(img,x,y) cimg_for9Y(img,y) cimg_for9X(img,x)
#define cimg_for9XZ(img,x,z) cimg_for9Z(img,z) cimg_for9X(img,x)
#define cimg_for9XV(img,x,v) cimg_for9V(img,v) cimg_for9X(img,x)
#define cimg_for9YZ(img,y,z) cimg_for9Z(img,z) cimg_for9Y(img,y)
#define cimg_for9YV(img,y,v) cimg_for9V(img,v) cimg_for9Y(img,y)
#define cimg_for9ZV(img,z,v) cimg_for9V(img,v) cimg_for9Z(img,z)
#define cimg_for9XYZ(img,x,y,z) cimg_for9Z(img,z) cimg_for9XY(img,x,y)
#define cimg_for9XZV(img,x,z,v) cimg_for9V(img,v) cimg_for9XZ(img,x,z)
#define cimg_for9YZV(img,y,z,v) cimg_for9V(img,v) cimg_for9YZ(img,y,z)
#define cimg_for9XYZV(img,x,y,z,v) cimg_for9V(img,v) cimg_for9XYZ(img,x,y,z)

#define cimg_for_in9(bound,i0,i1,i) \
  for (int i = (int)(i0)<0?0:(int)(i0), \
       _p4##i = i-4<0?0:i-4, \
       _p3##i = i-3<0?0:i-3, \
       _p2##i = i-2<0?0:i-2, \
       _p1##i = i-1<0?0:i-1, \
       _n1##i = i+1>=(int)(bound)?(int)(bound)-1:i+1, \
       _n2##i = i+2>=(int)(bound)?(int)(bound)-1:i+2, \
       _n3##i = i+3>=(int)(bound)?(int)(bound)-1:i+3, \
       _n4##i = i+4>=(int)(bound)?(int)(bound)-1:i+4; \
       i<=(int)(i1) && (_n4##i<(int)(bound) || _n3##i==--_n4##i || _n2##i==--_n3##i || _n1##i==--_n2##i || \
       i==(_n4##i = _n3##i = _n2##i = --_n1##i)); \
       _p4##i = _p3##i, _p3##i = _p2##i, _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i, ++_n3##i, ++_n4##i)
#define cimg_for_in9X(img,x0,x1,x) cimg_for_in9((img).width,x0,x1,x)
#define cimg_for_in9Y(img,y0,y1,y) cimg_for_in9((img).height,y0,y1,y)
#define cimg_for_in9Z(img,z0,z1,z) cimg_for_in9((img).depth,z0,z1,z)
#define cimg_for_in9V(img,v0,v1,v) cimg_for_in9((img).dim,v0,v1,v)
#define cimg_for_in9XY(img,x0,y0,x1,y1,x,y) cimg_for_in9Y(img,y0,y1,y) cimg_for_in9X(img,x0,x1,x)
#define cimg_for_in9XZ(img,x0,z0,x1,z1,x,z) cimg_for_in9Z(img,z0,z1,z) cimg_for_in9X(img,x0,x1,x)
#define cimg_for_in9XV(img,x0,v0,x1,v1,x,v) cimg_for_in9V(img,v0,v1,v) cimg_for_in9X(img,x0,x1,x)
#define cimg_for_in9YZ(img,y0,z0,y1,z1,y,z) cimg_for_in9Z(img,z0,z1,z) cimg_for_in9Y(img,y0,y1,y)
#define cimg_for_in9YV(img,y0,v0,y1,v1,y,v) cimg_for_in9V(img,v0,v1,v) cimg_for_in9Y(img,y0,y1,y)
#define cimg_for_in9ZV(img,z0,v0,z1,v1,z,v) cimg_for_in9V(img,v0,v1,v) cimg_for_in9Z(img,z0,z1,z)
#define cimg_for_in9XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_in9Z(img,z0,z1,z) cimg_for_in9XY(img,x0,y0,x1,y1,x,y)
#define cimg_for_in9XZV(img,x0,z0,v0,x1,y1,v1,x,z,v) cimg_for_in9V(img,v0,v1,v) cimg_for_in9XZ(img,x0,y0,x1,y1,x,z)
#define cimg_for_in9YZV(img,y0,z0,v0,y1,z1,v1,y,z,v) cimg_for_in9V(img,v0,v1,v) cimg_for_in9YZ(img,y0,z0,y1,z1,y,z)
#define cimg_for_in9XYZV(img,x0,y0,z0,v0,x1,y1,z1,v1,x,y,z,v) cimg_for_in9V(img,v0,v1,v) cimg_for_in9XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z)

#define cimg_for2x2(img,x,y,z,v,I) \
  cimg_for2((img).height,y) for (int x = 0, \
   _n1##x = (int)( \
   (I[0] = (img)(0,y,z,v)), \
   (I[2] = (img)(0,_n1##y,z,v)), \
   1>=(img).width?(int)((img).width)-1:1);  \
   (_n1##x<(int)((img).width) && ( \
   (I[1] = (img)(_n1##x,y,z,v)), \
   (I[3] = (img)(_n1##x,_n1##y,z,v)),1)) || \
   x==--_n1##x; \
   I[0] = I[1], \
   I[2] = I[3], \
   ++x, ++_n1##x)

#define cimg_for_in2x2(img,x0,y0,x1,y1,x,y,z,v,I) \
  cimg_for_in2((img).height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)(x0), \
   _n1##x = (int)( \
   (I[0] = (img)(x,y,z,v)), \
   (I[2] = (img)(x,_n1##y,z,v)), \
   x+1>=(int)(img).width?(int)((img).width)-1:x+1); \
   x<=(int)(x1) && ((_n1##x<(int)((img).width) && (  \
   (I[1] = (img)(_n1##x,y,z,v)), \
   (I[3] = (img)(_n1##x,_n1##y,z,v)),1)) || \
   x==--_n1##x); \
   I[0] = I[1], \
   I[2] = I[3], \
   ++x, ++_n1##x)

#define cimg_for3x3(img,x,y,z,v,I) \
  cimg_for3((img).height,y) for (int x = 0, \
   _p1##x = 0, \
   _n1##x = (int)( \
   (I[0] = I[1] = (img)(0,_p1##y,z,v)), \
   (I[3] = I[4] = (img)(0,y,z,v)), \
   (I[6] = I[7] = (img)(0,_n1##y,z,v)), \
   1>=(img).width?(int)((img).width)-1:1); \
   (_n1##x<(int)((img).width) && ( \
   (I[2] = (img)(_n1##x,_p1##y,z,v)), \
   (I[5] = (img)(_n1##x,y,z,v)), \
   (I[8] = (img)(_n1##x,_n1##y,z,v)),1)) || \
   x==--_n1##x; \
   I[0] = I[1], I[1] = I[2], \
   I[3] = I[4], I[4] = I[5], \
   I[6] = I[7], I[7] = I[8], \
   _p1##x = x++, ++_n1##x)

#define cimg_for_in3x3(img,x0,y0,x1,y1,x,y,z,v,I) \
  cimg_for_in3((img).height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)(x0), \
   _p1##x = x-1<0?0:x-1, \
   _n1##x = (int)( \
   (I[0] = (img)(_p1##x,_p1##y,z,v)), \
   (I[3] = (img)(_p1##x,y,z,v)), \
   (I[6] = (img)(_p1##x,_n1##y,z,v)), \
   (I[1] = (img)(x,_p1##y,z,v)), \
   (I[4] = (img)(x,y,z,v)), \
   (I[7] = (img)(x,_n1##y,z,v)), \
   x+1>=(int)(img).width?(int)((img).width)-1:x+1); \
   x<=(int)(x1) && ((_n1##x<(int)((img).width) && ( \
   (I[2] = (img)(_n1##x,_p1##y,z,v)), \
   (I[5] = (img)(_n1##x,y,z,v)), \
   (I[8] = (img)(_n1##x,_n1##y,z,v)),1)) || \
   x==--_n1##x);            \
   I[0] = I[1], I[1] = I[2], \
   I[3] = I[4], I[4] = I[5], \
   I[6] = I[7], I[7] = I[8], \
   _p1##x = x++, ++_n1##x)

#define cimg_for4x4(img,x,y,z,v,I) \
  cimg_for4((img).height,y) for (int x = 0, \
   _p1##x = 0, \
   _n1##x = 1>=(img).width?(int)((img).width)-1:1, \
   _n2##x = (int)( \
   (I[0] = I[1] = (img)(0,_p1##y,z,v)), \
   (I[4] = I[5] = (img)(0,y,z,v)), \
   (I[8] = I[9] = (img)(0,_n1##y,z,v)), \
   (I[12] = I[13] = (img)(0,_n2##y,z,v)), \
   (I[2] = (img)(_n1##x,_p1##y,z,v)), \
   (I[6] = (img)(_n1##x,y,z,v)), \
   (I[10] = (img)(_n1##x,_n1##y,z,v)), \
   (I[14] = (img)(_n1##x,_n2##y,z,v)), \
   2>=(img).width?(int)((img).width)-1:2); \
   (_n2##x<(int)((img).width) && ( \
   (I[3] = (img)(_n2##x,_p1##y,z,v)), \
   (I[7] = (img)(_n2##x,y,z,v)), \
   (I[11] = (img)(_n2##x,_n1##y,z,v)), \
   (I[15] = (img)(_n2##x,_n2##y,z,v)),1)) || \
   _n1##x==--_n2##x || x==(_n2##x = --_n1##x); \
   I[0] = I[1], I[1] = I[2], I[2] = I[3], \
   I[4] = I[5], I[5] = I[6], I[6] = I[7], \
   I[8] = I[9], I[9] = I[10], I[10] = I[11], \
   I[12] = I[13], I[13] = I[14], I[14] = I[15], \
   _p1##x = x++, ++_n1##x, ++_n2##x)

#define cimg_for_in4x4(img,x0,y0,x1,y1,x,y,z,v,I) \
  cimg_for_in4((img).height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)(x0), \
   _p1##x = x-1<0?0:x-1, \
   _n1##x = x+1>=(int)(img).width?(int)((img).width)-1:x+1, \
   _n2##x = (int)( \
   (I[0] = (img)(_p1##x,_p1##y,z,v)), \
   (I[4] = (img)(_p1##x,y,z,v)), \
   (I[8] = (img)(_p1##x,_n1##y,z,v)), \
   (I[12] = (img)(_p1##x,_n2##y,z,v)), \
   (I[1] = (img)(x,_p1##y,z,v)), \
   (I[5] = (img)(x,y,z,v)), \
   (I[9] = (img)(x,_n1##y,z,v)), \
   (I[13] = (img)(x,_n2##y,z,v)), \
   (I[2] = (img)(_n1##x,_p1##y,z,v)), \
   (I[6] = (img)(_n1##x,y,z,v)), \
   (I[10] = (img)(_n1##x,_n1##y,z,v)), \
   (I[14] = (img)(_n1##x,_n2##y,z,v)), \
   x+2>=(int)(img).width?(int)((img).width)-1:x+2); \
   x<=(int)(x1) && ((_n2##x<(int)((img).width) && ( \
   (I[3] = (img)(_n2##x,_p1##y,z,v)), \
   (I[7] = (img)(_n2##x,y,z,v)), \
   (I[11] = (img)(_n2##x,_n1##y,z,v)), \
   (I[15] = (img)(_n2##x,_n2##y,z,v)),1)) || \
   _n1##x==--_n2##x || x==(_n2##x = --_n1##x)); \
   I[0] = I[1], I[1] = I[2], I[2] = I[3], \
   I[4] = I[5], I[5] = I[6], I[6] = I[7], \
   I[8] = I[9], I[9] = I[10], I[10] = I[11], \
   I[12] = I[13], I[13] = I[14], I[14] = I[15], \
   _p1##x = x++, ++_n1##x, ++_n2##x)

#define cimg_for5x5(img,x,y,z,v,I) \
 cimg_for5((img).height,y) for (int x = 0, \
   _p2##x = 0, _p1##x = 0, \
   _n1##x = 1>=(img).width?(int)((img).width)-1:1, \
   _n2##x = (int)( \
   (I[0] = I[1] = I[2] = (img)(0,_p2##y,z,v)), \
   (I[5] = I[6] = I[7] = (img)(0,_p1##y,z,v)), \
   (I[10] = I[11] = I[12] = (img)(0,y,z,v)), \
   (I[15] = I[16] = I[17] = (img)(0,_n1##y,z,v)), \
   (I[20] = I[21] = I[22] = (img)(0,_n2##y,z,v)), \
   (I[3] = (img)(_n1##x,_p2##y,z,v)), \
   (I[8] = (img)(_n1##x,_p1##y,z,v)), \
   (I[13] = (img)(_n1##x,y,z,v)), \
   (I[18] = (img)(_n1##x,_n1##y,z,v)), \
   (I[23] = (img)(_n1##x,_n2##y,z,v)),     \
   2>=(img).width?(int)((img).width)-1:2); \
   (_n2##x<(int)((img).width) && ( \
   (I[4] = (img)(_n2##x,_p2##y,z,v)), \
   (I[9] = (img)(_n2##x,_p1##y,z,v)), \
   (I[14] = (img)(_n2##x,y,z,v)), \
   (I[19] = (img)(_n2##x,_n1##y,z,v)), \
   (I[24] = (img)(_n2##x,_n2##y,z,v)),1)) || \
   _n1##x==--_n2##x || x==(_n2##x = --_n1##x); \
   I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], \
   I[5] = I[6], I[6] = I[7], I[7] = I[8], I[8] = I[9], \
   I[10] = I[11], I[11] = I[12], I[12] = I[13], I[13] = I[14], \
   I[15] = I[16], I[16] = I[17], I[17] = I[18], I[18] = I[19], \
   I[20] = I[21], I[21] = I[22], I[22] = I[23], I[23] = I[24], \
   _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x)

#define cimg_for_in5x5(img,x0,y0,x1,y1,x,y,z,v,I) \
 cimg_for_in5((img).height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)(x0), \
   _p2##x = x-2<0?0:x-2, \
   _p1##x = x-1<0?0:x-1, \
   _n1##x = x+1>=(int)(img).width?(int)((img).width)-1:x+1, \
   _n2##x = (int)( \
   (I[0] = (img)(_p2##x,_p2##y,z,v)), \
   (I[5] = (img)(_p2##x,_p1##y,z,v)), \
   (I[10] = (img)(_p2##x,y,z,v)), \
   (I[15] = (img)(_p2##x,_n1##y,z,v)), \
   (I[20] = (img)(_p2##x,_n2##y,z,v)), \
   (I[1] = (img)(_p1##x,_p2##y,z,v)), \
   (I[6] = (img)(_p1##x,_p1##y,z,v)), \
   (I[11] = (img)(_p1##x,y,z,v)), \
   (I[16] = (img)(_p1##x,_n1##y,z,v)), \
   (I[21] = (img)(_p1##x,_n2##y,z,v)), \
   (I[2] = (img)(x,_p2##y,z,v)), \
   (I[7] = (img)(x,_p1##y,z,v)), \
   (I[12] = (img)(x,y,z,v)), \
   (I[17] = (img)(x,_n1##y,z,v)), \
   (I[22] = (img)(x,_n2##y,z,v)), \
   (I[3] = (img)(_n1##x,_p2##y,z,v)), \
   (I[8] = (img)(_n1##x,_p1##y,z,v)), \
   (I[13] = (img)(_n1##x,y,z,v)), \
   (I[18] = (img)(_n1##x,_n1##y,z,v)), \
   (I[23] = (img)(_n1##x,_n2##y,z,v)), \
   x+2>=(int)(img).width?(int)((img).width)-1:x+2); \
   x<=(int)(x1) && ((_n2##x<(int)((img).width) && ( \
   (I[4] = (img)(_n2##x,_p2##y,z,v)), \
   (I[9] = (img)(_n2##x,_p1##y,z,v)), \
   (I[14] = (img)(_n2##x,y,z,v)), \
   (I[19] = (img)(_n2##x,_n1##y,z,v)), \
   (I[24] = (img)(_n2##x,_n2##y,z,v)),1)) || \
   _n1##x==--_n2##x || x==(_n2##x = --_n1##x)); \
   I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], \
   I[5] = I[6], I[6] = I[7], I[7] = I[8], I[8] = I[9], \
   I[10] = I[11], I[11] = I[12], I[12] = I[13], I[13] = I[14], \
   I[15] = I[16], I[16] = I[17], I[17] = I[18], I[18] = I[19], \
   I[20] = I[21], I[21] = I[22], I[22] = I[23], I[23] = I[24], \
   _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x)

#define cimg_for6x6(img,x,y,z,v,I) \
 cimg_for6((img).height,y) for (int x = 0, \
   _p2##x = 0, _p1##x = 0, \
   _n1##x = 1>=(img).width?(int)((img).width)-1:1, \
   _n2##x = 2>=(img).width?(int)((img).width)-1:2, \
   _n3##x = (int)( \
   (I[0] = I[1] = I[2] = (img)(0,_p2##y,z,v)), \
   (I[6] = I[7] = I[8] = (img)(0,_p1##y,z,v)), \
   (I[12] = I[13] = I[14] = (img)(0,y,z,v)), \
   (I[18] = I[19] = I[20] = (img)(0,_n1##y,z,v)), \
   (I[24] = I[25] = I[26] = (img)(0,_n2##y,z,v)), \
   (I[30] = I[31] = I[32] = (img)(0,_n3##y,z,v)), \
   (I[3] = (img)(_n1##x,_p2##y,z,v)), \
   (I[9] = (img)(_n1##x,_p1##y,z,v)), \
   (I[15] = (img)(_n1##x,y,z,v)), \
   (I[21] = (img)(_n1##x,_n1##y,z,v)), \
   (I[27] = (img)(_n1##x,_n2##y,z,v)), \
   (I[33] = (img)(_n1##x,_n3##y,z,v)), \
   (I[4] = (img)(_n2##x,_p2##y,z,v)), \
   (I[10] = (img)(_n2##x,_p1##y,z,v)), \
   (I[16] = (img)(_n2##x,y,z,v)), \
   (I[22] = (img)(_n2##x,_n1##y,z,v)), \
   (I[28] = (img)(_n2##x,_n2##y,z,v)), \
   (I[34] = (img)(_n2##x,_n3##y,z,v)), \
   3>=(img).width?(int)((img).width)-1:3); \
   (_n3##x<(int)((img).width) && ( \
   (I[5] = (img)(_n3##x,_p2##y,z,v)), \
   (I[11] = (img)(_n3##x,_p1##y,z,v)), \
   (I[17] = (img)(_n3##x,y,z,v)), \
   (I[23] = (img)(_n3##x,_n1##y,z,v)), \
   (I[29] = (img)(_n3##x,_n2##y,z,v)), \
   (I[35] = (img)(_n3##x,_n3##y,z,v)),1)) || \
   _n2##x==--_n3##x || _n1##x==--_n2##x || x==(_n3## x = _n2##x = --_n1##x); \
   I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], I[4] = I[5], \
   I[6] = I[7], I[7] = I[8], I[8] = I[9], I[9] = I[10], I[10] = I[11], \
   I[12] = I[13], I[13] = I[14], I[14] = I[15], I[15] = I[16], I[16] = I[17], \
   I[18] = I[19], I[19] = I[20], I[20] = I[21], I[21] = I[22], I[22] = I[23], \
   I[24] = I[25], I[25] = I[26], I[26] = I[27], I[27] = I[28], I[28] = I[29], \
   I[30] = I[31], I[31] = I[32], I[32] = I[33], I[33] = I[34], I[34] = I[35], \
   _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x, ++_n3##x)

#define cimg_for_in6x6(img,x0,y0,x1,y1,x,y,z,v,I) \
  cimg_for_in6((img).height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)x0, \
   _p2##x = x-2<0?0:x-2, \
   _p1##x = x-1<0?0:x-1, \
   _n1##x = x+1>=(int)(img).width?(int)((img).width)-1:x+1, \
   _n2##x = x+2>=(int)(img).width?(int)((img).width)-1:x+2, \
   _n3##x = (int)( \
   (I[0] = (img)(_p2##x,_p2##y,z,v)), \
   (I[6] = (img)(_p2##x,_p1##y,z,v)), \
   (I[12] = (img)(_p2##x,y,z,v)), \
   (I[18] = (img)(_p2##x,_n1##y,z,v)), \
   (I[24] = (img)(_p2##x,_n2##y,z,v)), \
   (I[30] = (img)(_p2##x,_n3##y,z,v)), \
   (I[1] = (img)(_p1##x,_p2##y,z,v)), \
   (I[7] = (img)(_p1##x,_p1##y,z,v)), \
   (I[13] = (img)(_p1##x,y,z,v)), \
   (I[19] = (img)(_p1##x,_n1##y,z,v)), \
   (I[25] = (img)(_p1##x,_n2##y,z,v)), \
   (I[31] = (img)(_p1##x,_n3##y,z,v)), \
   (I[2] = (img)(x,_p2##y,z,v)), \
   (I[8] = (img)(x,_p1##y,z,v)), \
   (I[14] = (img)(x,y,z,v)), \
   (I[20] = (img)(x,_n1##y,z,v)), \
   (I[26] = (img)(x,_n2##y,z,v)), \
   (I[32] = (img)(x,_n3##y,z,v)), \
   (I[3] = (img)(_n1##x,_p2##y,z,v)), \
   (I[9] = (img)(_n1##x,_p1##y,z,v)), \
   (I[15] = (img)(_n1##x,y,z,v)), \
   (I[21] = (img)(_n1##x,_n1##y,z,v)), \
   (I[27] = (img)(_n1##x,_n2##y,z,v)), \
   (I[33] = (img)(_n1##x,_n3##y,z,v)), \
   (I[4] = (img)(_n2##x,_p2##y,z,v)), \
   (I[10] = (img)(_n2##x,_p1##y,z,v)), \
   (I[16] = (img)(_n2##x,y,z,v)), \
   (I[22] = (img)(_n2##x,_n1##y,z,v)), \
   (I[28] = (img)(_n2##x,_n2##y,z,v)), \
   (I[34] = (img)(_n2##x,_n3##y,z,v)), \
   x+3>=(int)(img).width?(int)((img).width)-1:x+3); \
   x<=(int)(x1) && ((_n3##x<(int)((img).width) && ( \
   (I[5] = (img)(_n3##x,_p2##y,z,v)), \
   (I[11] = (img)(_n3##x,_p1##y,z,v)), \
   (I[17] = (img)(_n3##x,y,z,v)), \
   (I[23] = (img)(_n3##x,_n1##y,z,v)), \
   (I[29] = (img)(_n3##x,_n2##y,z,v)), \
   (I[35] = (img)(_n3##x,_n3##y,z,v)),1)) || \
   _n2##x==--_n3##x || _n1##x==--_n2##x || x==(_n3## x = _n2##x = --_n1##x)); \
   I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], I[4] = I[5], \
   I[6] = I[7], I[7] = I[8], I[8] = I[9], I[9] = I[10], I[10] = I[11], \
   I[12] = I[13], I[13] = I[14], I[14] = I[15], I[15] = I[16], I[16] = I[17], \
   I[18] = I[19], I[19] = I[20], I[20] = I[21], I[21] = I[22], I[22] = I[23], \
   I[24] = I[25], I[25] = I[26], I[26] = I[27], I[27] = I[28], I[28] = I[29], \
   I[30] = I[31], I[31] = I[32], I[32] = I[33], I[33] = I[34], I[34] = I[35], \
   _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x, ++_n3##x)

#define cimg_for7x7(img,x,y,z,v,I) \
  cimg_for7((img).height,y) for (int x = 0, \
   _p3##x = 0, _p2##x = 0, _p1##x = 0, \
   _n1##x = 1>=(img).width?(int)((img).width)-1:1, \
   _n2##x = 2>=(img).width?(int)((img).width)-1:2, \
   _n3##x = (int)( \
   (I[0] = I[1] = I[2] = I[3] = (img)(0,_p3##y,z,v)), \
   (I[7] = I[8] = I[9] = I[10] = (img)(0,_p2##y,z,v)), \
   (I[14] = I[15] = I[16] = I[17] = (img)(0,_p1##y,z,v)), \
   (I[21] = I[22] = I[23] = I[24] = (img)(0,y,z,v)), \
   (I[28] = I[29] = I[30] = I[31] = (img)(0,_n1##y,z,v)), \
   (I[35] = I[36] = I[37] = I[38] = (img)(0,_n2##y,z,v)), \
   (I[42] = I[43] = I[44] = I[45] = (img)(0,_n3##y,z,v)), \
   (I[4] = (img)(_n1##x,_p3##y,z,v)), \
   (I[11] = (img)(_n1##x,_p2##y,z,v)), \
   (I[18] = (img)(_n1##x,_p1##y,z,v)), \
   (I[25] = (img)(_n1##x,y,z,v)), \
   (I[32] = (img)(_n1##x,_n1##y,z,v)), \
   (I[39] = (img)(_n1##x,_n2##y,z,v)), \
   (I[46] = (img)(_n1##x,_n3##y,z,v)), \
   (I[5] = (img)(_n2##x,_p3##y,z,v)), \
   (I[12] = (img)(_n2##x,_p2##y,z,v)), \
   (I[19] = (img)(_n2##x,_p1##y,z,v)), \
   (I[26] = (img)(_n2##x,y,z,v)), \
   (I[33] = (img)(_n2##x,_n1##y,z,v)), \
   (I[40] = (img)(_n2##x,_n2##y,z,v)), \
   (I[47] = (img)(_n2##x,_n3##y,z,v)), \
   3>=(img).width?(int)((img).width)-1:3); \
   (_n3##x<(int)((img).width) && ( \
   (I[6] = (img)(_n3##x,_p3##y,z,v)), \
   (I[13] = (img)(_n3##x,_p2##y,z,v)), \
   (I[20] = (img)(_n3##x,_p1##y,z,v)), \
   (I[27] = (img)(_n3##x,y,z,v)), \
   (I[34] = (img)(_n3##x,_n1##y,z,v)), \
   (I[41] = (img)(_n3##x,_n2##y,z,v)), \
   (I[48] = (img)(_n3##x,_n3##y,z,v)),1)) || \
   _n2##x==--_n3##x || _n1##x==--_n2##x || x==(_n3##x = _n2##x = --_n1##x); \
   I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], I[4] = I[5], I[5] = I[6], \
   I[7] = I[8], I[8] = I[9], I[9] = I[10], I[10] = I[11], I[11] = I[12], I[12] = I[13], \
   I[14] = I[15], I[15] = I[16], I[16] = I[17], I[17] = I[18], I[18] = I[19], I[19] = I[20], \
   I[21] = I[22], I[22] = I[23], I[23] = I[24], I[24] = I[25], I[25] = I[26], I[26] = I[27], \
   I[28] = I[29], I[29] = I[30], I[30] = I[31], I[31] = I[32], I[32] = I[33], I[33] = I[34], \
   I[35] = I[36], I[36] = I[37], I[37] = I[38], I[38] = I[39], I[39] = I[40], I[40] = I[41], \
   I[42] = I[43], I[43] = I[44], I[44] = I[45], I[45] = I[46], I[46] = I[47], I[47] = I[48], \
   _p3##x = _p2##x, _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x, ++_n3##x)

#define cimg_for_in7x7(img,x0,y0,x1,y1,x,y,z,v,I) \
  cimg_for_in7((img).height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)(x0), \
   _p3##x = x-3<0?0:x-3, \
   _p2##x = x-2<0?0:x-2, \
   _p1##x = x-1<0?0:x-1, \
   _n1##x = x+1>=(int)(img).width?(int)((img).width)-1:x+1, \
   _n2##x = x+2>=(int)(img).width?(int)((img).width)-1:x+2, \
   _n3##x = (int)( \
   (I[0] = (img)(_p3##x,_p3##y,z,v)), \
   (I[7] = (img)(_p3##x,_p2##y,z,v)), \
   (I[14] = (img)(_p3##x,_p1##y,z,v)), \
   (I[21] = (img)(_p3##x,y,z,v)), \
   (I[28] = (img)(_p3##x,_n1##y,z,v)), \
   (I[35] = (img)(_p3##x,_n2##y,z,v)), \
   (I[42] = (img)(_p3##x,_n3##y,z,v)), \
   (I[1] = (img)(_p2##x,_p3##y,z,v)), \
   (I[8] = (img)(_p2##x,_p2##y,z,v)), \
   (I[15] = (img)(_p2##x,_p1##y,z,v)), \
   (I[22] = (img)(_p2##x,y,z,v)), \
   (I[29] = (img)(_p2##x,_n1##y,z,v)), \
   (I[36] = (img)(_p2##x,_n2##y,z,v)), \
   (I[43] = (img)(_p2##x,_n3##y,z,v)), \
   (I[2] = (img)(_p1##x,_p3##y,z,v)), \
   (I[9] = (img)(_p1##x,_p2##y,z,v)), \
   (I[16] = (img)(_p1##x,_p1##y,z,v)), \
   (I[23] = (img)(_p1##x,y,z,v)), \
   (I[30] = (img)(_p1##x,_n1##y,z,v)), \
   (I[37] = (img)(_p1##x,_n2##y,z,v)), \
   (I[44] = (img)(_p1##x,_n3##y,z,v)), \
   (I[3] = (img)(x,_p3##y,z,v)), \
   (I[10] = (img)(x,_p2##y,z,v)), \
   (I[17] = (img)(x,_p1##y,z,v)), \
   (I[24] = (img)(x,y,z,v)), \
   (I[31] = (img)(x,_n1##y,z,v)), \
   (I[38] = (img)(x,_n2##y,z,v)), \
   (I[45] = (img)(x,_n3##y,z,v)), \
   (I[4] = (img)(_n1##x,_p3##y,z,v)), \
   (I[11] = (img)(_n1##x,_p2##y,z,v)), \
   (I[18] = (img)(_n1##x,_p1##y,z,v)), \
   (I[25] = (img)(_n1##x,y,z,v)), \
   (I[32] = (img)(_n1##x,_n1##y,z,v)), \
   (I[39] = (img)(_n1##x,_n2##y,z,v)), \
   (I[46] = (img)(_n1##x,_n3##y,z,v)), \
   (I[5] = (img)(_n2##x,_p3##y,z,v)), \
   (I[12] = (img)(_n2##x,_p2##y,z,v)), \
   (I[19] = (img)(_n2##x,_p1##y,z,v)), \
   (I[26] = (img)(_n2##x,y,z,v)), \
   (I[33] = (img)(_n2##x,_n1##y,z,v)), \
   (I[40] = (img)(_n2##x,_n2##y,z,v)), \
   (I[47] = (img)(_n2##x,_n3##y,z,v)), \
   x+3>=(int)(img).width?(int)((img).width)-1:x+3); \
   x<=(int)(x1) && ((_n3##x<(int)((img).width) && ( \
   (I[6] = (img)(_n3##x,_p3##y,z,v)), \
   (I[13] = (img)(_n3##x,_p2##y,z,v)), \
   (I[20] = (img)(_n3##x,_p1##y,z,v)), \
   (I[27] = (img)(_n3##x,y,z,v)), \
   (I[34] = (img)(_n3##x,_n1##y,z,v)), \
   (I[41] = (img)(_n3##x,_n2##y,z,v)), \
   (I[48] = (img)(_n3##x,_n3##y,z,v)),1)) || \
   _n2##x==--_n3##x || _n1##x==--_n2##x || x==(_n3##x = _n2##x = --_n1##x)); \
   I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], I[4] = I[5], I[5] = I[6], \
   I[7] = I[8], I[8] = I[9], I[9] = I[10], I[10] = I[11], I[11] = I[12], I[12] = I[13], \
   I[14] = I[15], I[15] = I[16], I[16] = I[17], I[17] = I[18], I[18] = I[19], I[19] = I[20], \
   I[21] = I[22], I[22] = I[23], I[23] = I[24], I[24] = I[25], I[25] = I[26], I[26] = I[27], \
   I[28] = I[29], I[29] = I[30], I[30] = I[31], I[31] = I[32], I[32] = I[33], I[33] = I[34], \
   I[35] = I[36], I[36] = I[37], I[37] = I[38], I[38] = I[39], I[39] = I[40], I[40] = I[41], \
   I[42] = I[43], I[43] = I[44], I[44] = I[45], I[45] = I[46], I[46] = I[47], I[47] = I[48], \
   _p3##x = _p2##x, _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x, ++_n3##x)

#define cimg_for8x8(img,x,y,z,v,I) \
  cimg_for8((img).height,y) for (int x = 0, \
   _p3##x = 0, _p2##x = 0, _p1##x = 0, \
   _n1##x = 1>=((img).width)?(int)((img).width)-1:1, \
   _n2##x = 2>=((img).width)?(int)((img).width)-1:2, \
   _n3##x = 3>=((img).width)?(int)((img).width)-1:3, \
   _n4##x = (int)( \
   (I[0] = I[1] = I[2] = I[3] = (img)(0,_p3##y,z,v)), \
   (I[8] = I[9] = I[10] = I[11] = (img)(0,_p2##y,z,v)), \
   (I[16] = I[17] = I[18] = I[19] = (img)(0,_p1##y,z,v)), \
   (I[24] = I[25] = I[26] = I[27] = (img)(0,y,z,v)), \
   (I[32] = I[33] = I[34] = I[35] = (img)(0,_n1##y,z,v)), \
   (I[40] = I[41] = I[42] = I[43] = (img)(0,_n2##y,z,v)), \
   (I[48] = I[49] = I[50] = I[51] = (img)(0,_n3##y,z,v)), \
   (I[56] = I[57] = I[58] = I[59] = (img)(0,_n4##y,z,v)), \
   (I[4] = (img)(_n1##x,_p3##y,z,v)), \
   (I[12] = (img)(_n1##x,_p2##y,z,v)), \
   (I[20] = (img)(_n1##x,_p1##y,z,v)), \
   (I[28] = (img)(_n1##x,y,z,v)), \
   (I[36] = (img)(_n1##x,_n1##y,z,v)), \
   (I[44] = (img)(_n1##x,_n2##y,z,v)), \
   (I[52] = (img)(_n1##x,_n3##y,z,v)), \
   (I[60] = (img)(_n1##x,_n4##y,z,v)), \
   (I[5] = (img)(_n2##x,_p3##y,z,v)), \
   (I[13] = (img)(_n2##x,_p2##y,z,v)), \
   (I[21] = (img)(_n2##x,_p1##y,z,v)), \
   (I[29] = (img)(_n2##x,y,z,v)), \
   (I[37] = (img)(_n2##x,_n1##y,z,v)), \
   (I[45] = (img)(_n2##x,_n2##y,z,v)), \
   (I[53] = (img)(_n2##x,_n3##y,z,v)), \
   (I[61] = (img)(_n2##x,_n4##y,z,v)), \
   (I[6] = (img)(_n3##x,_p3##y,z,v)), \
   (I[14] = (img)(_n3##x,_p2##y,z,v)), \
   (I[22] = (img)(_n3##x,_p1##y,z,v)), \
   (I[30] = (img)(_n3##x,y,z,v)), \
   (I[38] = (img)(_n3##x,_n1##y,z,v)), \
   (I[46] = (img)(_n3##x,_n2##y,z,v)), \
   (I[54] = (img)(_n3##x,_n3##y,z,v)), \
   (I[62] = (img)(_n3##x,_n4##y,z,v)), \
   4>=((img).width)?(int)((img).width)-1:4); \
   (_n4##x<(int)((img).width) && ( \
   (I[7] = (img)(_n4##x,_p3##y,z,v)), \
   (I[15] = (img)(_n4##x,_p2##y,z,v)), \
   (I[23] = (img)(_n4##x,_p1##y,z,v)), \
   (I[31] = (img)(_n4##x,y,z,v)), \
   (I[39] = (img)(_n4##x,_n1##y,z,v)), \
   (I[47] = (img)(_n4##x,_n2##y,z,v)), \
   (I[55] = (img)(_n4##x,_n3##y,z,v)), \
   (I[63] = (img)(_n4##x,_n4##y,z,v)),1)) || \
   _n3##x==--_n4##x || _n2##x==--_n3##x || _n1##x==--_n2##x || x==(_n4##x = _n3##x = _n2##x = --_n1##x); \
   I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], I[4] = I[5], I[5] = I[6], I[6] = I[7], \
   I[8] = I[9], I[9] = I[10], I[10] = I[11], I[11] = I[12], I[12] = I[13], I[13] = I[14], I[14] = I[15], \
   I[16] = I[17], I[17] = I[18], I[18] = I[19], I[19] = I[20], I[20] = I[21], I[21] = I[22], I[22] = I[23], \
   I[24] = I[25], I[25] = I[26], I[26] = I[27], I[27] = I[28], I[28] = I[29], I[29] = I[30], I[30] = I[31], \
   I[32] = I[33], I[33] = I[34], I[34] = I[35], I[35] = I[36], I[36] = I[37], I[37] = I[38], I[38] = I[39], \
   I[40] = I[41], I[41] = I[42], I[42] = I[43], I[43] = I[44], I[44] = I[45], I[45] = I[46], I[46] = I[47], \
   I[48] = I[49], I[49] = I[50], I[50] = I[51], I[51] = I[52], I[52] = I[53], I[53] = I[54], I[54] = I[55], \
   I[56] = I[57], I[57] = I[58], I[58] = I[59], I[59] = I[60], I[60] = I[61], I[61] = I[62], I[62] = I[63], \
   _p3##x = _p2##x, _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x, ++_n3##x, ++_n4##x)

#define cimg_for_in8x8(img,x0,y0,x1,y1,x,y,z,v,I) \
  cimg_for_in8((img).height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)(x0), \
   _p3##x = x-3<0?0:x-3, \
   _p2##x = x-2<0?0:x-2, \
   _p1##x = x-1<0?0:x-1, \
   _n1##x = x+1>=(int)((img).width)?(int)((img).width)-1:x+1, \
   _n2##x = x+2>=(int)((img).width)?(int)((img).width)-1:x+2, \
   _n3##x = x+3>=(int)((img).width)?(int)((img).width)-1:x+3, \
   _n4##x = (int)( \
   (I[0] = (img)(_p3##x,_p3##y,z,v)), \
   (I[8] = (img)(_p3##x,_p2##y,z,v)), \
   (I[16] = (img)(_p3##x,_p1##y,z,v)), \
   (I[24] = (img)(_p3##x,y,z,v)), \
   (I[32] = (img)(_p3##x,_n1##y,z,v)), \
   (I[40] = (img)(_p3##x,_n2##y,z,v)), \
   (I[48] = (img)(_p3##x,_n3##y,z,v)), \
   (I[56] = (img)(_p3##x,_n4##y,z,v)), \
   (I[1] = (img)(_p2##x,_p3##y,z,v)), \
   (I[9] = (img)(_p2##x,_p2##y,z,v)), \
   (I[17] = (img)(_p2##x,_p1##y,z,v)), \
   (I[25] = (img)(_p2##x,y,z,v)), \
   (I[33] = (img)(_p2##x,_n1##y,z,v)), \
   (I[41] = (img)(_p2##x,_n2##y,z,v)), \
   (I[49] = (img)(_p2##x,_n3##y,z,v)), \
   (I[57] = (img)(_p2##x,_n4##y,z,v)), \
   (I[2] = (img)(_p1##x,_p3##y,z,v)), \
   (I[10] = (img)(_p1##x,_p2##y,z,v)), \
   (I[18] = (img)(_p1##x,_p1##y,z,v)), \
   (I[26] = (img)(_p1##x,y,z,v)), \
   (I[34] = (img)(_p1##x,_n1##y,z,v)), \
   (I[42] = (img)(_p1##x,_n2##y,z,v)), \
   (I[50] = (img)(_p1##x,_n3##y,z,v)), \
   (I[58] = (img)(_p1##x,_n4##y,z,v)), \
   (I[3] = (img)(x,_p3##y,z,v)), \
   (I[11] = (img)(x,_p2##y,z,v)), \
   (I[19] = (img)(x,_p1##y,z,v)), \
   (I[27] = (img)(x,y,z,v)), \
   (I[35] = (img)(x,_n1##y,z,v)), \
   (I[43] = (img)(x,_n2##y,z,v)), \
   (I[51] = (img)(x,_n3##y,z,v)), \
   (I[59] = (img)(x,_n4##y,z,v)), \
   (I[4] = (img)(_n1##x,_p3##y,z,v)), \
   (I[12] = (img)(_n1##x,_p2##y,z,v)), \
   (I[20] = (img)(_n1##x,_p1##y,z,v)), \
   (I[28] = (img)(_n1##x,y,z,v)), \
   (I[36] = (img)(_n1##x,_n1##y,z,v)), \
   (I[44] = (img)(_n1##x,_n2##y,z,v)), \
   (I[52] = (img)(_n1##x,_n3##y,z,v)), \
   (I[60] = (img)(_n1##x,_n4##y,z,v)), \
   (I[5] = (img)(_n2##x,_p3##y,z,v)), \
   (I[13] = (img)(_n2##x,_p2##y,z,v)), \
   (I[21] = (img)(_n2##x,_p1##y,z,v)), \
   (I[29] = (img)(_n2##x,y,z,v)), \
   (I[37] = (img)(_n2##x,_n1##y,z,v)), \
   (I[45] = (img)(_n2##x,_n2##y,z,v)), \
   (I[53] = (img)(_n2##x,_n3##y,z,v)), \
   (I[61] = (img)(_n2##x,_n4##y,z,v)), \
   (I[6] = (img)(_n3##x,_p3##y,z,v)), \
   (I[14] = (img)(_n3##x,_p2##y,z,v)), \
   (I[22] = (img)(_n3##x,_p1##y,z,v)), \
   (I[30] = (img)(_n3##x,y,z,v)), \
   (I[38] = (img)(_n3##x,_n1##y,z,v)), \
   (I[46] = (img)(_n3##x,_n2##y,z,v)), \
   (I[54] = (img)(_n3##x,_n3##y,z,v)), \
   (I[62] = (img)(_n3##x,_n4##y,z,v)), \
   x+4>=(int)((img).width)?(int)((img).width)-1:x+4); \
   x<=(int)(x1) && ((_n4##x<(int)((img).width) && ( \
   (I[7] = (img)(_n4##x,_p3##y,z,v)), \
   (I[15] = (img)(_n4##x,_p2##y,z,v)), \
   (I[23] = (img)(_n4##x,_p1##y,z,v)), \
   (I[31] = (img)(_n4##x,y,z,v)), \
   (I[39] = (img)(_n4##x,_n1##y,z,v)), \
   (I[47] = (img)(_n4##x,_n2##y,z,v)), \
   (I[55] = (img)(_n4##x,_n3##y,z,v)), \
   (I[63] = (img)(_n4##x,_n4##y,z,v)),1)) || \
   _n3##x==--_n4##x || _n2##x==--_n3##x || _n1##x==--_n2##x || x==(_n4##x = _n3##x = _n2##x = --_n1##x)); \
   I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], I[4] = I[5], I[5] = I[6], I[6] = I[7], \
   I[8] = I[9], I[9] = I[10], I[10] = I[11], I[11] = I[12], I[12] = I[13], I[13] = I[14], I[14] = I[15], \
   I[16] = I[17], I[17] = I[18], I[18] = I[19], I[19] = I[20], I[20] = I[21], I[21] = I[22], I[22] = I[23], \
   I[24] = I[25], I[25] = I[26], I[26] = I[27], I[27] = I[28], I[28] = I[29], I[29] = I[30], I[30] = I[31], \
   I[32] = I[33], I[33] = I[34], I[34] = I[35], I[35] = I[36], I[36] = I[37], I[37] = I[38], I[38] = I[39], \
   I[40] = I[41], I[41] = I[42], I[42] = I[43], I[43] = I[44], I[44] = I[45], I[45] = I[46], I[46] = I[47], \
   I[48] = I[49], I[49] = I[50], I[50] = I[51], I[51] = I[52], I[52] = I[53], I[53] = I[54], I[54] = I[55], \
   I[56] = I[57], I[57] = I[58], I[58] = I[59], I[59] = I[60], I[60] = I[61], I[61] = I[62], I[62] = I[63], \
   _p3##x = _p2##x, _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x, ++_n3##x, ++_n4##x)

#define cimg_for9x9(img,x,y,z,v,I) \
  cimg_for9((img).height,y) for (int x = 0, \
   _p4##x = 0, _p3##x = 0, _p2##x = 0, _p1##x = 0, \
   _n1##x = 1>=((img).width)?(int)((img).width)-1:1, \
   _n2##x = 2>=((img).width)?(int)((img).width)-1:2, \
   _n3##x = 3>=((img).width)?(int)((img).width)-1:3, \
   _n4##x = (int)( \
   (I[0] = I[1] = I[2] = I[3] = I[4] = (img)(0,_p4##y,z,v)), \
   (I[9] = I[10] = I[11] = I[12] = I[13] = (img)(0,_p3##y,z,v)), \
   (I[18] = I[19] = I[20] = I[21] = I[22] = (img)(0,_p2##y,z,v)), \
   (I[27] = I[28] = I[29] = I[30] = I[31] = (img)(0,_p1##y,z,v)), \
   (I[36] = I[37] = I[38] = I[39] = I[40] = (img)(0,y,z,v)), \
   (I[45] = I[46] = I[47] = I[48] = I[49] = (img)(0,_n1##y,z,v)), \
   (I[54] = I[55] = I[56] = I[57] = I[58] = (img)(0,_n2##y,z,v)), \
   (I[63] = I[64] = I[65] = I[66] = I[67] = (img)(0,_n3##y,z,v)), \
   (I[72] = I[73] = I[74] = I[75] = I[76] = (img)(0,_n4##y,z,v)), \
   (I[5] = (img)(_n1##x,_p4##y,z,v)), \
   (I[14] = (img)(_n1##x,_p3##y,z,v)), \
   (I[23] = (img)(_n1##x,_p2##y,z,v)), \
   (I[32] = (img)(_n1##x,_p1##y,z,v)), \
   (I[41] = (img)(_n1##x,y,z,v)), \
   (I[50] = (img)(_n1##x,_n1##y,z,v)), \
   (I[59] = (img)(_n1##x,_n2##y,z,v)), \
   (I[68] = (img)(_n1##x,_n3##y,z,v)), \
   (I[77] = (img)(_n1##x,_n4##y,z,v)), \
   (I[6] = (img)(_n2##x,_p4##y,z,v)), \
   (I[15] = (img)(_n2##x,_p3##y,z,v)), \
   (I[24] = (img)(_n2##x,_p2##y,z,v)), \
   (I[33] = (img)(_n2##x,_p1##y,z,v)), \
   (I[42] = (img)(_n2##x,y,z,v)), \
   (I[51] = (img)(_n2##x,_n1##y,z,v)), \
   (I[60] = (img)(_n2##x,_n2##y,z,v)), \
   (I[69] = (img)(_n2##x,_n3##y,z,v)), \
   (I[78] = (img)(_n2##x,_n4##y,z,v)), \
   (I[7] = (img)(_n3##x,_p4##y,z,v)), \
   (I[16] = (img)(_n3##x,_p3##y,z,v)), \
   (I[25] = (img)(_n3##x,_p2##y,z,v)), \
   (I[34] = (img)(_n3##x,_p1##y,z,v)), \
   (I[43] = (img)(_n3##x,y,z,v)), \
   (I[52] = (img)(_n3##x,_n1##y,z,v)), \
   (I[61] = (img)(_n3##x,_n2##y,z,v)), \
   (I[70] = (img)(_n3##x,_n3##y,z,v)), \
   (I[79] = (img)(_n3##x,_n4##y,z,v)), \
   4>=((img).width)?(int)((img).width)-1:4); \
   (_n4##x<(int)((img).width) && ( \
   (I[8] = (img)(_n4##x,_p4##y,z,v)), \
   (I[17] = (img)(_n4##x,_p3##y,z,v)), \
   (I[26] = (img)(_n4##x,_p2##y,z,v)), \
   (I[35] = (img)(_n4##x,_p1##y,z,v)), \
   (I[44] = (img)(_n4##x,y,z,v)), \
   (I[53] = (img)(_n4##x,_n1##y,z,v)), \
   (I[62] = (img)(_n4##x,_n2##y,z,v)), \
   (I[71] = (img)(_n4##x,_n3##y,z,v)), \
   (I[80] = (img)(_n4##x,_n4##y,z,v)),1)) || \
   _n3##x==--_n4##x || _n2##x==--_n3##x || _n1##x==--_n2##x || x==(_n4##x = _n3##x = _n2##x = --_n1##x); \
   I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], I[4] = I[5], I[5] = I[6], I[6] = I[7], I[7] = I[8], \
   I[9] = I[10], I[10] = I[11], I[11] = I[12], I[12] = I[13], I[13] = I[14], I[14] = I[15], I[15] = I[16], I[16] = I[17], \
   I[18] = I[19], I[19] = I[20], I[20] = I[21], I[21] = I[22], I[22] = I[23], I[23] = I[24], I[24] = I[25], I[25] = I[26], \
   I[27] = I[28], I[28] = I[29], I[29] = I[30], I[30] = I[31], I[31] = I[32], I[32] = I[33], I[33] = I[34], I[34] = I[35], \
   I[36] = I[37], I[37] = I[38], I[38] = I[39], I[39] = I[40], I[40] = I[41], I[41] = I[42], I[42] = I[43], I[43] = I[44], \
   I[45] = I[46], I[46] = I[47], I[47] = I[48], I[48] = I[49], I[49] = I[50], I[50] = I[51], I[51] = I[52], I[52] = I[53], \
   I[54] = I[55], I[55] = I[56], I[56] = I[57], I[57] = I[58], I[58] = I[59], I[59] = I[60], I[60] = I[61], I[61] = I[62], \
   I[63] = I[64], I[64] = I[65], I[65] = I[66], I[66] = I[67], I[67] = I[68], I[68] = I[69], I[69] = I[70], I[70] = I[71], \
   I[72] = I[73], I[73] = I[74], I[74] = I[75], I[75] = I[76], I[76] = I[77], I[77] = I[78], I[78] = I[79], I[79] = I[80], \
   _p4##x = _p3##x, _p3##x = _p2##x, _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x, ++_n3##x, ++_n4##x)

#define cimg_for_in9x9(img,x0,y0,x1,y1,x,y,z,v,I) \
  cimg_for_in9((img).height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)(x0), \
   _p4##x = x-4<0?0:x-4, \
   _p3##x = x-3<0?0:x-3, \
   _p2##x = x-2<0?0:x-2, \
   _p1##x = x-1<0?0:x-1, \
   _n1##x = x+1>=(int)((img).width)?(int)((img).width)-1:x+1, \
   _n2##x = x+2>=(int)((img).width)?(int)((img).width)-1:x+2, \
   _n3##x = x+3>=(int)((img).width)?(int)((img).width)-1:x+3, \
   _n4##x = (int)( \
   (I[0] = (img)(_p4##x,_p4##y,z,v)), \
   (I[9] = (img)(_p4##x,_p3##y,z,v)), \
   (I[18] = (img)(_p4##x,_p2##y,z,v)), \
   (I[27] = (img)(_p4##x,_p1##y,z,v)), \
   (I[36] = (img)(_p4##x,y,z,v)), \
   (I[45] = (img)(_p4##x,_n1##y,z,v)), \
   (I[54] = (img)(_p4##x,_n2##y,z,v)), \
   (I[63] = (img)(_p4##x,_n3##y,z,v)), \
   (I[72] = (img)(_p4##x,_n4##y,z,v)), \
   (I[1] = (img)(_p3##x,_p4##y,z,v)), \
   (I[10] = (img)(_p3##x,_p3##y,z,v)), \
   (I[19] = (img)(_p3##x,_p2##y,z,v)), \
   (I[28] = (img)(_p3##x,_p1##y,z,v)), \
   (I[37] = (img)(_p3##x,y,z,v)), \
   (I[46] = (img)(_p3##x,_n1##y,z,v)), \
   (I[55] = (img)(_p3##x,_n2##y,z,v)), \
   (I[64] = (img)(_p3##x,_n3##y,z,v)), \
   (I[73] = (img)(_p3##x,_n4##y,z,v)), \
   (I[2] = (img)(_p2##x,_p4##y,z,v)), \
   (I[11] = (img)(_p2##x,_p3##y,z,v)), \
   (I[20] = (img)(_p2##x,_p2##y,z,v)), \
   (I[29] = (img)(_p2##x,_p1##y,z,v)), \
   (I[38] = (img)(_p2##x,y,z,v)), \
   (I[47] = (img)(_p2##x,_n1##y,z,v)), \
   (I[56] = (img)(_p2##x,_n2##y,z,v)), \
   (I[65] = (img)(_p2##x,_n3##y,z,v)), \
   (I[74] = (img)(_p2##x,_n4##y,z,v)), \
   (I[3] = (img)(_p1##x,_p4##y,z,v)), \
   (I[12] = (img)(_p1##x,_p3##y,z,v)), \
   (I[21] = (img)(_p1##x,_p2##y,z,v)), \
   (I[30] = (img)(_p1##x,_p1##y,z,v)), \
   (I[39] = (img)(_p1##x,y,z,v)), \
   (I[48] = (img)(_p1##x,_n1##y,z,v)), \
   (I[57] = (img)(_p1##x,_n2##y,z,v)), \
   (I[66] = (img)(_p1##x,_n3##y,z,v)), \
   (I[75] = (img)(_p1##x,_n4##y,z,v)), \
   (I[4] = (img)(x,_p4##y,z,v)), \
   (I[13] = (img)(x,_p3##y,z,v)), \
   (I[22] = (img)(x,_p2##y,z,v)), \
   (I[31] = (img)(x,_p1##y,z,v)), \
   (I[40] = (img)(x,y,z,v)), \
   (I[49] = (img)(x,_n1##y,z,v)), \
   (I[58] = (img)(x,_n2##y,z,v)), \
   (I[67] = (img)(x,_n3##y,z,v)), \
   (I[76] = (img)(x,_n4##y,z,v)), \
   (I[5] = (img)(_n1##x,_p4##y,z,v)), \
   (I[14] = (img)(_n1##x,_p3##y,z,v)), \
   (I[23] = (img)(_n1##x,_p2##y,z,v)), \
   (I[32] = (img)(_n1##x,_p1##y,z,v)), \
   (I[41] = (img)(_n1##x,y,z,v)), \
   (I[50] = (img)(_n1##x,_n1##y,z,v)), \
   (I[59] = (img)(_n1##x,_n2##y,z,v)), \
   (I[68] = (img)(_n1##x,_n3##y,z,v)), \
   (I[77] = (img)(_n1##x,_n4##y,z,v)), \
   (I[6] = (img)(_n2##x,_p4##y,z,v)), \
   (I[15] = (img)(_n2##x,_p3##y,z,v)), \
   (I[24] = (img)(_n2##x,_p2##y,z,v)), \
   (I[33] = (img)(_n2##x,_p1##y,z,v)), \
   (I[42] = (img)(_n2##x,y,z,v)), \
   (I[51] = (img)(_n2##x,_n1##y,z,v)), \
   (I[60] = (img)(_n2##x,_n2##y,z,v)), \
   (I[69] = (img)(_n2##x,_n3##y,z,v)), \
   (I[78] = (img)(_n2##x,_n4##y,z,v)), \
   (I[7] = (img)(_n3##x,_p4##y,z,v)), \
   (I[16] = (img)(_n3##x,_p3##y,z,v)), \
   (I[25] = (img)(_n3##x,_p2##y,z,v)), \
   (I[34] = (img)(_n3##x,_p1##y,z,v)), \
   (I[43] = (img)(_n3##x,y,z,v)), \
   (I[52] = (img)(_n3##x,_n1##y,z,v)), \
   (I[61] = (img)(_n3##x,_n2##y,z,v)), \
   (I[70] = (img)(_n3##x,_n3##y,z,v)), \
   (I[79] = (img)(_n3##x,_n4##y,z,v)), \
   x+4>=(int)((img).width)?(int)((img).width)-1:x+4); \
   x<=(int)(x1) && ((_n4##x<(int)((img).width) && ( \
   (I[8] = (img)(_n4##x,_p4##y,z,v)), \
   (I[17] = (img)(_n4##x,_p3##y,z,v)), \
   (I[26] = (img)(_n4##x,_p2##y,z,v)), \
   (I[35] = (img)(_n4##x,_p1##y,z,v)), \
   (I[44] = (img)(_n4##x,y,z,v)), \
   (I[53] = (img)(_n4##x,_n1##y,z,v)), \
   (I[62] = (img)(_n4##x,_n2##y,z,v)), \
   (I[71] = (img)(_n4##x,_n3##y,z,v)), \
   (I[80] = (img)(_n4##x,_n4##y,z,v)),1)) || \
   _n3##x==--_n4##x || _n2##x==--_n3##x || _n1##x==--_n2##x || x==(_n4##x = _n3##x = _n2##x = --_n1##x)); \
   I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], I[4] = I[5], I[5] = I[6], I[6] = I[7], I[7] = I[8], \
   I[9] = I[10], I[10] = I[11], I[11] = I[12], I[12] = I[13], I[13] = I[14], I[14] = I[15], I[15] = I[16], I[16] = I[17], \
   I[18] = I[19], I[19] = I[20], I[20] = I[21], I[21] = I[22], I[22] = I[23], I[23] = I[24], I[24] = I[25], I[25] = I[26], \
   I[27] = I[28], I[28] = I[29], I[29] = I[30], I[30] = I[31], I[31] = I[32], I[32] = I[33], I[33] = I[34], I[34] = I[35], \
   I[36] = I[37], I[37] = I[38], I[38] = I[39], I[39] = I[40], I[40] = I[41], I[41] = I[42], I[42] = I[43], I[43] = I[44], \
   I[45] = I[46], I[46] = I[47], I[47] = I[48], I[48] = I[49], I[49] = I[50], I[50] = I[51], I[51] = I[52], I[52] = I[53], \
   I[54] = I[55], I[55] = I[56], I[56] = I[57], I[57] = I[58], I[58] = I[59], I[59] = I[60], I[60] = I[61], I[61] = I[62], \
   I[63] = I[64], I[64] = I[65], I[65] = I[66], I[66] = I[67], I[67] = I[68], I[68] = I[69], I[69] = I[70], I[70] = I[71], \
   I[72] = I[73], I[73] = I[74], I[74] = I[75], I[75] = I[76], I[76] = I[77], I[77] = I[78], I[78] = I[79], I[79] = I[80], \
   _p4##x = _p3##x, _p3##x = _p2##x, _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x, ++_n3##x, ++_n4##x)

#define cimg_for2x2x2(img,x,y,z,v,I) \
 cimg_for2((img).depth,z) cimg_for2((img).height,y) for (int x = 0, \
   _n1##x = (int)( \
   (I[0] = (img)(0,y,z,v)), \
   (I[2] = (img)(0,_n1##y,z,v)), \
   (I[4] = (img)(0,y,_n1##z,v)), \
   (I[6] = (img)(0,_n1##y,_n1##z,v)), \
   1>=(img).width?(int)((img).width)-1:1); \
   (_n1##x<(int)((img).width) && ( \
   (I[1] = (img)(_n1##x,y,z,v)), \
   (I[3] = (img)(_n1##x,_n1##y,z,v)), \
   (I[5] = (img)(_n1##x,y,_n1##z,v)), \
   (I[7] = (img)(_n1##x,_n1##y,_n1##z,v)),1)) || \
   x==--_n1##x; \
   I[0] = I[1], I[2] = I[3], I[4] = I[5], I[6] = I[7], \
   ++x, ++_n1##x)

#define cimg_for_in2x2x2(img,x0,y0,z0,x1,y1,z1,x,y,z,v,I) \
 cimg_for_in2((img).depth,z0,z1,z) cimg_for_in2((img).height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)(x0), \
   _n1##x = (int)( \
   (I[0] = (img)(x,y,z,v)), \
   (I[2] = (img)(x,_n1##y,z,v)), \
   (I[4] = (img)(x,y,_n1##z,v)), \
   (I[6] = (img)(x,_n1##y,_n1##z,v)), \
   x+1>=(int)(img).width?(int)((img).width)-1:x+1); \
   x<=(int)(x1) && ((_n1##x<(int)((img).width) && ( \
   (I[1] = (img)(_n1##x,y,z,v)), \
   (I[3] = (img)(_n1##x,_n1##y,z,v)), \
   (I[5] = (img)(_n1##x,y,_n1##z,v)), \
   (I[7] = (img)(_n1##x,_n1##y,_n1##z,v)),1)) || \
   x==--_n1##x); \
   I[0] = I[1], I[2] = I[3], I[4] = I[5], I[6] = I[7], \
   ++x, ++_n1##x)

#define cimg_for3x3x3(img,x,y,z,v,I) \
 cimg_for3((img).depth,z) cimg_for3((img).height,y) for (int x = 0, \
   _p1##x = 0, \
   _n1##x = (int)( \
   (I[0] = I[1] = (img)(0,_p1##y,_p1##z,v)), \
   (I[3] = I[4] = (img)(0,y,_p1##z,v)),  \
   (I[6] = I[7] = (img)(0,_n1##y,_p1##z,v)), \
   (I[9] = I[10] = (img)(0,_p1##y,z,v)), \
   (I[12] = I[13] = (img)(0,y,z,v)), \
   (I[15] = I[16] = (img)(0,_n1##y,z,v)), \
   (I[18] = I[19] = (img)(0,_p1##y,_n1##z,v)), \
   (I[21] = I[22] = (img)(0,y,_n1##z,v)), \
   (I[24] = I[25] = (img)(0,_n1##y,_n1##z,v)), \
   1>=(img).width?(int)((img).width)-1:1); \
   (_n1##x<(int)((img).width) && ( \
   (I[2] = (img)(_n1##x,_p1##y,_p1##z,v)), \
   (I[5] = (img)(_n1##x,y,_p1##z,v)), \
   (I[8] = (img)(_n1##x,_n1##y,_p1##z,v)), \
   (I[11] = (img)(_n1##x,_p1##y,z,v)), \
   (I[14] = (img)(_n1##x,y,z,v)), \
   (I[17] = (img)(_n1##x,_n1##y,z,v)), \
   (I[20] = (img)(_n1##x,_p1##y,_n1##z,v)), \
   (I[23] = (img)(_n1##x,y,_n1##z,v)), \
   (I[26] = (img)(_n1##x,_n1##y,_n1##z,v)),1)) || \
   x==--_n1##x; \
   I[0] = I[1], I[1] = I[2], I[3] = I[4], I[4] = I[5], I[6] = I[7], I[7] = I[8], \
   I[9] = I[10], I[10] = I[11], I[12] = I[13], I[13] = I[14], I[15] = I[16], I[16] = I[17], \
   I[18] = I[19], I[19] = I[20], I[21] = I[22], I[22] = I[23], I[24] = I[25], I[25] = I[26], \
   _p1##x = x++, ++_n1##x)

#define cimg_for_in3x3x3(img,x0,y0,z0,x1,y1,z1,x,y,z,v,I) \
 cimg_for_in3((img).depth,z0,z1,z) cimg_for_in3((img).height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)(x0), \
   _p1##x = x-1<0?0:x-1, \
   _n1##x = (int)( \
   (I[0] = (img)(_p1##x,_p1##y,_p1##z,v)), \
   (I[3] = (img)(_p1##x,y,_p1##z,v)),  \
   (I[6] = (img)(_p1##x,_n1##y,_p1##z,v)), \
   (I[9] = (img)(_p1##x,_p1##y,z,v)), \
   (I[12] = (img)(_p1##x,y,z,v)), \
   (I[15] = (img)(_p1##x,_n1##y,z,v)), \
   (I[18] = (img)(_p1##x,_p1##y,_n1##z,v)), \
   (I[21] = (img)(_p1##x,y,_n1##z,v)), \
   (I[24] = (img)(_p1##x,_n1##y,_n1##z,v)), \
   (I[1] = (img)(x,_p1##y,_p1##z,v)), \
   (I[4] = (img)(x,y,_p1##z,v)),  \
   (I[7] = (img)(x,_n1##y,_p1##z,v)), \
   (I[10] = (img)(x,_p1##y,z,v)), \
   (I[13] = (img)(x,y,z,v)), \
   (I[16] = (img)(x,_n1##y,z,v)), \
   (I[19] = (img)(x,_p1##y,_n1##z,v)), \
   (I[22] = (img)(x,y,_n1##z,v)), \
   (I[25] = (img)(x,_n1##y,_n1##z,v)), \
   x+1>=(int)(img).width?(int)((img).width)-1:x+1); \
   x<=(int)(x1) && ((_n1##x<(int)((img).width) && ( \
   (I[2] = (img)(_n1##x,_p1##y,_p1##z,v)), \
   (I[5] = (img)(_n1##x,y,_p1##z,v)), \
   (I[8] = (img)(_n1##x,_n1##y,_p1##z,v)), \
   (I[11] = (img)(_n1##x,_p1##y,z,v)), \
   (I[14] = (img)(_n1##x,y,z,v)), \
   (I[17] = (img)(_n1##x,_n1##y,z,v)), \
   (I[20] = (img)(_n1##x,_p1##y,_n1##z,v)), \
   (I[23] = (img)(_n1##x,y,_n1##z,v)), \
   (I[26] = (img)(_n1##x,_n1##y,_n1##z,v)),1)) || \
   x==--_n1##x); \
   I[0] = I[1], I[1] = I[2], I[3] = I[4], I[4] = I[5], I[6] = I[7], I[7] = I[8], \
   I[9] = I[10], I[10] = I[11], I[12] = I[13], I[13] = I[14], I[15] = I[16], I[16] = I[17], \
   I[18] = I[19], I[19] = I[20], I[21] = I[22], I[22] = I[23], I[24] = I[25], I[25] = I[26], \
   _p1##x = x++, ++_n1##x)

/*------------------------------------------------
 #
 #
 #  Definition of the cimg_library:: namespace
 #
 #
 -------------------------------------------------*/
//! This namespace encompasses all classes and functions of the %CImg library.
/**
   This namespace is defined to avoid functions and class names collisions
   that could happen with the include of other C++ header files.
   Anyway, it should not happen often and you should reasonnably start most of your
   %CImg-based programs with
   \code
   #include "CImg.h"
   using namespace cimg_library;
   \endcode
   to simplify the declaration of %CImg Library variables afterwards.
**/
namespace cimg_library {

  // Declare the only four classes of the CImg Library.
  //
  template<typename T=float> struct CImg;
  template<typename T=float> struct CImgList;
  struct CImgDisplay;
  struct CImgException;

  // (Pre)declare the cimg namespace.
  // This is not the complete namespace declaration. It only contains some
  // necessary stuffs to ensure a correct declaration order of classes and functions
  // defined afterwards.
  //
  namespace cimg {

#ifdef cimg_use_vt100
    const char t_normal[] = { 0x1b,'[','0',';','0',';','0','m','\0' };
    const char t_red[] = { 0x1b,'[','4',';','3','1',';','5','9','m','\0' };
    const char t_bold[] = { 0x1b,'[','1','m','\0' };
    const char t_purple[] = { 0x1b,'[','0',';','3','5',';','5','9','m','\0' };
    const char t_green[] = { 0x1b,'[','0',';','3','2',';','5','9','m','\0' };
#else
    const char t_normal[] = { '\0' };
    const char *const t_red = cimg::t_normal, *const t_bold = cimg::t_normal,
      *const t_purple = cimg::t_normal, *const t_green = cimg::t_normal;
#endif

    inline void info();

    //! Get/set the current CImg exception mode.
    /**
       The way error messages are handled by CImg can be changed dynamically, using this function.
       Possible values are :
       - 0 to hide debug messages (quiet mode, but exceptions are still thrown).
       - 1 to display debug messages on standard error (console).
       - 2 to display debug messages in modal windows (default behavior).
       - 3 to do as 1 + add extra warnings (may slow down the code !).
       - 4 to do as 2 + add extra warnings (may slow down the code !).
     **/
    inline unsigned int& exception_mode() { static unsigned int mode = cimg_debug; return mode; }

    inline int dialog(const char *title, const char *msg, const char *button1_txt="OK",
                      const char *button2_txt=0, const char *button3_txt=0,
                      const char *button4_txt=0, const char *button5_txt=0,
                      const char *button6_txt=0, const bool centering=false);
  }

  /*----------------------------------------------
   #
   # Definition of the CImgException structures
   #
   ----------------------------------------------*/
  //! Instances of this class are thrown when errors occur during a %CImg library function call.
  /**
     \section ex1 Overview

      CImgException is the base class of %CImg exceptions.
      Exceptions are thrown by the %CImg Library when an error occured in a %CImg library function call.
      CImgException is seldom thrown itself. Children classes that specify the kind of error encountered
      are generally used instead. These sub-classes are :

      - \b CImgInstanceException : Thrown when the instance associated to the called %CImg function is not
      correctly defined. Generally, this exception is thrown when one tries to process \a empty images. The example
      below will throw a \a CImgInstanceException.
      \code
      CImg<float> img;        // Construct an empty image.
      img.blur(10);           // Try to blur the image.
      \endcode

      - \b CImgArgumentException : Thrown when one of the arguments given to the called %CImg function is not correct.
      Generally, this exception is thrown when arguments passed to the function are outside an admissible range of values.
      The example below will throw a \a CImgArgumentException.
      \code
      CImg<float> img(100,100,1,3);   // Define a 100x100 color image with float pixels.
      img = 0;                     // Try to fill pixels from the 0 pointer (invalid argument to operator=() ).
      \endcode

      - \b CImgIOException : Thrown when an error occured when trying to load or save image files.
      The example below will throw a \a CImgIOException.
      \code
      CImg<float> img("file_doesnt_exist.jpg");    // Try to load a file that doesn't exist.
      \endcode

      - \b CImgDisplayException : Thrown when an error occured when trying to display an image in a window.
      This exception is thrown when image display request cannot be satisfied.

      The parent class CImgException may be thrown itself when errors that cannot be classified in one of
      the above type occur. It is recommended not to throw CImgExceptions yourself, since there are normally
      reserved to %CImg Library functions.
      \b CImgInstanceException, \b CImgArgumentException, \b CImgIOException and \b CImgDisplayException are simple
      subclasses of CImgException and are thus not detailled more in this reference documentation.

      \section ex2 Exception handling

      When an error occurs, the %CImg Library first displays the error in a modal window.
      Then, it throws an instance of the corresponding exception class, generally leading the program to stop
      (this is the default behavior).
      You can bypass this default behavior by handling the exceptions yourself,
      using a code block <tt>try { ... } catch() { ... }</tt>.
      In this case, you can avoid the apparition of the modal window, by
      defining the environment variable <tt>cimg_debug</tt> to 0 before including the %CImg header file.
      The example below shows how to cleanly handle %CImg Library exceptions :
      \code
      #define cimg_debug 0     // Disable modal window in CImg exceptions.
      #define "CImg.h"
      int main() {
        try {
          ...; // Here, do what you want.
        }
        catch (CImgInstanceException &e) {
          std::fprintf(stderr,"CImg Library Error : %s",e.message);  // Display your own error message
          ...                                                        // Do what you want now.
        }
      }
      \endcode
  **/
  struct CImgException {
#define _cimg_exception_err(etype,disp_flag) \
  cimg_std::va_list ap; va_start(ap,format); cimg_std::vsprintf(message,format,ap); va_end(ap); \
  switch (cimg::exception_mode()) { \
  case 0 : break; \
  case 2 : case 4 : try { cimg::dialog(etype,message,"Abort"); } catch (CImgException&) { \
    cimg_std::fprintf(cimg_stdout,"\n%s# %s%s :\n%s\n\n",cimg::t_red,etype,cimg::t_normal,message); \
  } break; \
  default : cimg_std::fprintf(cimg_stdout,"\n%s# %s%s :\n%s\n\n",cimg::t_red,etype,cimg::t_normal,message); \
  } \
  if (cimg::exception_mode()>=3) cimg_library::cimg::info();

    char message[1024]; //!< Message associated with the error that thrown the exception.
    CImgException() { message[0]='\0'; }
    CImgException(const char *format, ...) { _cimg_exception_err("CImgException",true); }
  };

  // The \ref CImgInstanceException class is used to throw an exception related
  // to a non suitable instance encountered in a library function call.
  struct CImgInstanceException: public CImgException {
    CImgInstanceException(const char *format, ...) { _cimg_exception_err("CImgInstanceException",true); }
  };

  // The \ref CImgArgumentException class is used to throw an exception related
  // to invalid arguments encountered in a library function call.
  struct CImgArgumentException: public CImgException {
    CImgArgumentException(const char *format, ...) { _cimg_exception_err("CImgArgumentException",true); }
  };

  // The \ref CImgIOException class is used to throw an exception related
  // to Input/Output file problems encountered in a library function call.
  struct CImgIOException: public CImgException {
    CImgIOException(const char *format, ...) { _cimg_exception_err("CImgIOException",true); }
  };

  // The CImgDisplayException class is used to throw an exception related to display problems
  // encountered in a library function call.
  struct CImgDisplayException: public CImgException {
    CImgDisplayException(const char *format, ...) { _cimg_exception_err("CImgDisplayException",false); }
  };

  // The CImgWarningException class is used to throw an exception for warnings
  // encountered in a library function call.
  struct CImgWarningException: public CImgException {
    CImgWarningException(const char *format, ...) { _cimg_exception_err("CImgWarningException",false); }
  };

  /*-------------------------------------
   #
   # Definition of the namespace 'cimg'
   #
   --------------------------------------*/
  //! Namespace that encompasses \a low-level functions and variables of the %CImg Library.
  /**
     Most of the functions and variables within this namespace are used by the library for low-level processing.
     Nevertheless, documented variables and functions of this namespace may be used safely in your own source code.

     \warning Never write <tt>using namespace cimg_library::cimg;</tt> in your source code, since a lot of functions of the
     <tt>cimg::</tt> namespace have prototypes similar to standard C functions that could defined in the global namespace <tt>::</tt>.
  **/
  namespace cimg {

    // Define the traits that will be used to determine the best data type to work with.
    //
    template<typename T> struct type {
      static const char* string() {
        static const char* s[] = { "unknown",   "unknown8",   "unknown16",  "unknown24",
                                   "unknown32", "unknown40",  "unknown48",  "unknown56",
                                   "unknown64", "unknown72",  "unknown80",  "unknown88",
                                   "unknown96", "unknown104", "unknown112", "unknown120",
                                   "unknown128" };
        return s[(sizeof(T)<17)?sizeof(T):0];
      }
      static bool is_float() { return false; }
      static T min() { return (T)-1>0?(T)0:(T)-1<<(8*sizeof(T)-1); }
      static T max() { return (T)-1>0?(T)-1:~((T)-1<<(8*sizeof(T)-1)); }
      static const char* format() { return "%s"; }
      static const char* format(const T val) { static const char *s = "unknown"; return s; }
    };

    template<> struct type<bool> {
      static const char* string() { static const char *const s = "bool"; return s; }
      static bool is_float() { return false; }
      static bool min() { return false; }
      static bool max() { return true; }
      static const char* format() { return "%s"; }
      static const char* format(const bool val) { static const char* s[] = { "false", "true" }; return s[val?1:0]; }
    };

    template<> struct type<unsigned char> {
      static const char* string() { static const char *const s = "unsigned char"; return s; }
      static bool is_float() { return false; }
      static unsigned char min() { return 0; }
      static unsigned char max() { return (unsigned char)~0U; }
      static const char* format() { return "%u"; }
      static unsigned int format(const unsigned char val) { return (unsigned int)val; }
    };

    template<> struct type<char> {
      static const char* string() { static const char *const s = "char"; return s; }
      static bool is_float() { return false; }
      static char min() { return (char)(-1L<<(8*sizeof(char)-1)); }
      static char max() { return ~((char)(-1L<<(8*sizeof(char)-1))); }
      static const char* format() { return "%d"; }
      static int format(const char val) { return (int)val; }
    };

    template<> struct type<signed char> {
      static const char* string() { static const char *const s = "signed char"; return s; }
      static bool is_float() { return false; }
      static signed char min() { return (signed char)(-1L<<(8*sizeof(signed char)-1)); }
      static signed char max() { return ~((signed char)(-1L<<(8*sizeof(signed char)-1))); }
      static const char* format() { return "%d"; }
      static unsigned int format(const signed char val) { return (int)val; }
    };

    template<> struct type<unsigned short> {
      static const char* string() { static const char *const s = "unsigned short"; return s; }
      static bool is_float() { return false; }
      static unsigned short min() { return 0; }
      static unsigned short max() { return (unsigned short)~0U; }
      static const char* format() { return "%u"; }
      static unsigned int format(const unsigned short val) { return (unsigned int)val; }
    };

    template<> struct type<short> {
      static const char* string() { static const char *const s = "short"; return s; }
      static bool is_float() { return false; }
      static short min() { return (short)(-1L<<(8*sizeof(short)-1)); }
      static short max() { return ~((short)(-1L<<(8*sizeof(short)-1))); }
      static const char* format() { return "%d"; }
      static int format(const short val) { return (int)val; }
    };

    template<> struct type<unsigned int> {
      static const char* string() { static const char *const s = "unsigned int"; return s; }
      static bool is_float() { return false; }
      static unsigned int min() { return 0; }
      static unsigned int max() { return (unsigned int)~0U; }
      static const char* format() { return "%u"; }
      static unsigned int format(const unsigned int val) { return val; }
    };

    template<> struct type<int> {
      static const char* string() { static const char *const s = "int"; return s; }
      static bool is_float() { return false; }
      static int min() { return (int)(-1L<<(8*sizeof(int)-1)); }
      static int max() { return ~((int)(-1L<<(8*sizeof(int)-1))); }
      static const char* format() { return "%d"; }
      static int format(const int val) { return val; }
    };

    template<> struct type<unsigned long> {
      static const char* string() { static const char *const s = "unsigned long"; return s; }
      static bool is_float() { return false; }
      static unsigned long min() { return 0; }
      static unsigned long max() { return (unsigned long)~0UL; }
      static const char* format() { return "%lu"; }
      static unsigned long format(const unsigned long val) { return val; }
    };

    template<> struct type<long> {
      static const char* string() { static const char *const s = "long"; return s; }
      static bool is_float() { return false; }
      static long min() { return (long)(-1L<<(8*sizeof(long)-1)); }
      static long max() { return ~((long)(-1L<<(8*sizeof(long)-1))); }
      static const char* format() { return "%ld"; }
      static long format(const long val) { return val; }
    };

    template<> struct type<float> {
      static const char* string() { static const char *const s = "float"; return s; }
      static bool is_float() { return true; }
      static float min() { return -3.4E38f; }
      static float max() { return  3.4E38f; }
      static const char* format() { return "%g"; }
      static double format(const float val) { return (double)val; }
    };

    template<> struct type<double> {
      static const char* string() { static const char *const s = "double"; return s; }
      static bool is_float() { return true; }
      static double min() { return -1.7E308; }
      static double max() { return  1.7E308; }
      static const char* format() { return "%g"; }
      static double format(const double val) { return val; }
    };

    template<typename T, typename t> struct superset { typedef T type; };
    template<> struct superset<bool,unsigned char> { typedef unsigned char type; };
    template<> struct superset<bool,char> { typedef char type; };
    template<> struct superset<bool,signed char> { typedef signed char type; };
    template<> struct superset<bool,unsigned short> { typedef unsigned short type; };
    template<> struct superset<bool,short> { typedef short type; };
    template<> struct superset<bool,unsigned int> { typedef unsigned int type; };
    template<> struct superset<bool,int> { typedef int type; };
    template<> struct superset<bool,unsigned long> { typedef unsigned long type; };
    template<> struct superset<bool,long> { typedef long type; };
    template<> struct superset<bool,float> { typedef float type; };
    template<> struct superset<bool,double> { typedef double type; };
    template<> struct superset<unsigned char,char> { typedef short type; };
    template<> struct superset<unsigned char,signed char> { typedef short type; };
    template<> struct superset<unsigned char,unsigned short> { typedef unsigned short type; };
    template<> struct superset<unsigned char,short> { typedef short type; };
    template<> struct superset<unsigned char,unsigned int> { typedef unsigned int type; };
    template<> struct superset<unsigned char,int> { typedef int type; };
    template<> struct superset<unsigned char,unsigned long> { typedef unsigned long type; };
    template<> struct superset<unsigned char,long> { typedef long type; };
    template<> struct superset<unsigned char,float> { typedef float type; };
    template<> struct superset<unsigned char,double> { typedef double type; };
    template<> struct superset<signed char,unsigned char> { typedef short type; };
    template<> struct superset<signed char,char> { typedef short type; };
    template<> struct superset<signed char,unsigned short> { typedef int type; };
    template<> struct superset<signed char,short> { typedef short type; };
    template<> struct superset<signed char,unsigned int> { typedef long type; };
    template<> struct superset<signed char,int> { typedef int type; };
    template<> struct superset<signed char,unsigned long> { typedef long type; };
    template<> struct superset<signed char,long> { typedef long type; };
    template<> struct superset<signed char,float> { typedef float type; };
    template<> struct superset<signed char,double> { typedef double type; };
    template<> struct superset<char,unsigned char> { typedef short type; };
    template<> struct superset<char,signed char> { typedef short type; };
    template<> struct superset<char,unsigned short> { typedef int type; };
    template<> struct superset<char,short> { typedef short type; };
    template<> struct superset<char,unsigned int> { typedef long type; };
    template<> struct superset<char,int> { typedef int type; };
    template<> struct superset<char,unsigned long> { typedef long type; };
    template<> struct superset<char,long> { typedef long type; };
    template<> struct superset<char,float> { typedef float type; };
    template<> struct superset<char,double> { typedef double type; };
    template<> struct superset<unsigned short,char> { typedef int type; };
    template<> struct superset<unsigned short,signed char> { typedef int type; };
    template<> struct superset<unsigned short,short> { typedef int type; };
    template<> struct superset<unsigned short,unsigned int> { typedef unsigned int type; };
    template<> struct superset<unsigned short,int> { typedef int type; };
    template<> struct superset<unsigned short,unsigned long> { typedef unsigned long type; };
    template<> struct superset<unsigned short,long> { typedef long type; };
    template<> struct superset<unsigned short,float> { typedef float type; };
    template<> struct superset<unsigned short,double> { typedef double type; };
    template<> struct superset<short,unsigned short> { typedef int type; };
    template<> struct superset<short,unsigned int> { typedef long type; };
    template<> struct superset<short,int> { typedef int type; };
    template<> struct superset<short,unsigned long> { typedef long type; };
    template<> struct superset<short,long> { typedef long type; };
    template<> struct superset<short,float> { typedef float type; };
    template<> struct superset<short,double> { typedef double type; };
    template<> struct superset<unsigned int,char> { typedef long type; };
    template<> struct superset<unsigned int,signed char> { typedef long type; };
    template<> struct superset<unsigned int,short> { typedef long type; };
    template<> struct superset<unsigned int,int> { typedef long type; };
    template<> struct superset<unsigned int,unsigned long> { typedef unsigned long type; };
    template<> struct superset<unsigned int,long> { typedef long type; };
    template<> struct superset<unsigned int,float> { typedef float type; };
    template<> struct superset<unsigned int,double> { typedef double type; };
    template<> struct superset<int,unsigned int> { typedef long type; };
    template<> struct superset<int,unsigned long> { typedef long type; };
    template<> struct superset<int,long> { typedef long type; };
    template<> struct superset<int,float> { typedef float type; };
    template<> struct superset<int,double> { typedef double type; };
    template<> struct superset<unsigned long,char> { typedef long type; };
    template<> struct superset<unsigned long,signed char> { typedef long type; };
    template<> struct superset<unsigned long,short> { typedef long type; };
    template<> struct superset<unsigned long,int> { typedef long type; };
    template<> struct superset<unsigned long,long> { typedef long type; };
    template<> struct superset<unsigned long,float> { typedef float type; };
    template<> struct superset<unsigned long,double> { typedef double type; };
    template<> struct superset<long,float> { typedef float type; };
    template<> struct superset<long,double> { typedef double type; };
    template<> struct superset<float,double> { typedef double type; };

    template<typename t1, typename t2, typename t3> struct superset2 {
      typedef typename superset<t1, typename superset<t2,t3>::type>::type type;
    };

    template<typename t1, typename t2, typename t3, typename t4> struct superset3 {
      typedef typename superset<t1, typename superset2<t2,t3,t4>::type>::type type;
    };

    template<typename t1, typename t2> struct last { typedef t2 type; };

#define _cimg_Tuchar  typename cimg::superset<T,unsigned char>::type
#define _cimg_Tint    typename cimg::superset<T,int>::type
#define _cimg_Tfloat  typename cimg::superset<T,float>::type
#define _cimg_Tdouble typename cimg::superset<T,double>::type
#define _cimg_Tt      typename cimg::superset<T,t>::type

    // Define internal library variables.
    //
#if cimg_display==1
    struct X11info {
      volatile unsigned int nb_wins;
      pthread_t*       event_thread;
      CImgDisplay*     wins[1024];
      Display*         display;
      unsigned int     nb_bits;
      GC*              gc;
      bool             blue_first;
      bool             byte_order;
      bool             shm_enabled;
#ifdef cimg_use_xrandr
      XRRScreenSize *resolutions;
      Rotation curr_rotation;
      unsigned int curr_resolution;
      unsigned int nb_resolutions;
#endif
      X11info():nb_wins(0),event_thread(0),display(0),
                nb_bits(0),gc(0),blue_first(false),byte_order(false),shm_enabled(false) {
#ifdef cimg_use_xrandr
        resolutions = 0;
        curr_rotation = 0;
        curr_resolution = nb_resolutions = 0;
#endif
      }
    };
#if defined(cimg_module)
    X11info& X11attr();
#elif defined(cimg_main)
    X11info& X11attr() { static X11info val; return val; }
#else
    inline X11info& X11attr() { static X11info val; return val; }
#endif

#elif cimg_display==2
    struct Win32info {
      HANDLE wait_event;
      Win32info() { wait_event = CreateEvent(0,FALSE,FALSE,0); }
    };
#if defined(cimg_module)
    Win32info& Win32attr();
#elif defined(cimg_main)
    Win32info& Win32attr() { static Win32info val; return val; }
#else
    inline Win32info& Win32attr() { static Win32info val; return val; }
#endif

#elif cimg_display==3
    struct CarbonInfo {
      MPCriticalRegionID windowListCR; // Protects access to the list of windows
      int windowCount;                 // Count of displays used on the screen
      pthread_t event_thread;          // The background event thread
      MPSemaphoreID sync_event;        // Event used to perform tasks synchronizations
      MPSemaphoreID wait_event;        // Event used to notify that new events occured on the display
      MPQueueID com_queue;             // The message queue
      CarbonInfo(): windowCount(0),event_thread(0),sync_event(0),com_queue(0) {
        if (MPCreateCriticalRegion(&windowListCR) != noErr) // Create the critical region
          throw CImgDisplayException("MPCreateCriticalRegion failed.");
        if (MPCreateSemaphore(1, 0, &sync_event) != noErr) // Create the inter-thread sync object
          throw CImgDisplayException("MPCreateSemaphore failed.");
        if (MPCreateSemaphore(1, 0, &wait_event) != noErr) // Create the event sync object
          throw CImgDisplayException("MPCreateSemaphore failed.");
        if (MPCreateQueue(&com_queue) != noErr) // Create the shared queue
          throw CImgDisplayException("MPCreateQueue failed.");
      }
      ~CarbonInfo() {
        if (event_thread != 0) { // Terminates the resident thread, if needed
          pthread_cancel(event_thread);
          pthread_join(event_thread, NULL);
          event_thread = 0;
        }
        if (MPDeleteCriticalRegion(windowListCR) != noErr) // Delete the critical region
          throw CImgDisplayException("MPDeleteCriticalRegion failed.");
        if (MPDeleteSemaphore(wait_event) != noErr) // Delete the event sync event
          throw CImgDisplayException("MPDeleteEvent failed.");
        if (MPDeleteSemaphore(sync_event) != noErr) // Delete the inter-thread sync event
          throw CImgDisplayException("MPDeleteEvent failed.");
        if (MPDeleteQueue(com_queue) != noErr) // Delete the shared queue
          throw CImgDisplayException("MPDeleteQueue failed.");
      }
    };
#if defined(cimg_module)
    CarbonInfo& CarbonAttr();
#elif defined(cimg_main)
    CarbonInfo CarbonAttr() { static CarbonInfo val; return val; }
#else
    inline CarbonInfo& CarbonAttr() { static CarbonInfo val; return val; }
#endif
#endif

#if cimg_display==1
    // Keycodes for X11-based graphical systems.
    //
    const unsigned int keyESC        = XK_Escape;
    const unsigned int keyF1         = XK_F1;
    const unsigned int keyF2         = XK_F2;
    const unsigned int keyF3         = XK_F3;
    const unsigned int keyF4         = XK_F4;
    const unsigned int keyF5         = XK_F5;
    const unsigned int keyF6         = XK_F6;
    const unsigned int keyF7         = XK_F7;
    const unsigned int keyF8         = XK_F8;
    const unsigned int keyF9         = XK_F9;
    const unsigned int keyF10        = XK_F10;
    const unsigned int keyF11        = XK_F11;
    const unsigned int keyF12        = XK_F12;
    const unsigned int keyPAUSE      = XK_Pause;
    const unsigned int key1          = XK_1;
    const unsigned int key2          = XK_2;
    const unsigned int key3          = XK_3;
    const unsigned int key4          = XK_4;
    const unsigned int key5          = XK_5;
    const unsigned int key6          = XK_6;
    const unsigned int key7          = XK_7;
    const unsigned int key8          = XK_8;
    const unsigned int key9          = XK_9;
    const unsigned int key0          = XK_0;
    const unsigned int keyBACKSPACE  = XK_BackSpace;
    const unsigned int keyINSERT     = XK_Insert;
    const unsigned int keyHOME       = XK_Home;
    const unsigned int keyPAGEUP     = XK_Page_Up;
    const unsigned int keyTAB        = XK_Tab;
    const unsigned int keyQ          = XK_q;
    const unsigned int keyW          = XK_w;
    const unsigned int keyE          = XK_e;
    const unsigned int keyR          = XK_r;
    const unsigned int keyT          = XK_t;
    const unsigned int keyY          = XK_y;
    const unsigned int keyU          = XK_u;
    const unsigned int keyI          = XK_i;
    const unsigned int keyO          = XK_o;
    const unsigned int keyP          = XK_p;
    const unsigned int keyDELETE     = XK_Delete;
    const unsigned int keyEND        = XK_End;
    const unsigned int keyPAGEDOWN   = XK_Page_Down;
    const unsigned int keyCAPSLOCK   = XK_Caps_Lock;
    const unsigned int keyA          = XK_a;
    const unsigned int keyS          = XK_s;
    const unsigned int keyD          = XK_d;
    const unsigned int keyF          = XK_f;
    const unsigned int keyG          = XK_g;
    const unsigned int keyH          = XK_h;
    const unsigned int keyJ          = XK_j;
    const unsigned int keyK          = XK_k;
    const unsigned int keyL          = XK_l;
    const unsigned int keyENTER      = XK_Return;
    const unsigned int keySHIFTLEFT  = XK_Shift_L;
    const unsigned int keyZ          = XK_z;
    const unsigned int keyX          = XK_x;
    const unsigned int keyC          = XK_c;
    const unsigned int keyV          = XK_v;
    const unsigned int keyB          = XK_b;
    const unsigned int keyN          = XK_n;
    const unsigned int keyM          = XK_m;
    const unsigned int keySHIFTRIGHT = XK_Shift_R;
    const unsigned int keyARROWUP    = XK_Up;
    const unsigned int keyCTRLLEFT   = XK_Control_L;
    const unsigned int keyAPPLEFT    = XK_Super_L;
    const unsigned int keyALT        = XK_Alt_L;
    const unsigned int keySPACE      = XK_space;
    const unsigned int keyALTGR      = XK_Alt_R;
    const unsigned int keyAPPRIGHT   = XK_Super_R;
    const unsigned int keyMENU       = XK_Menu;
    const unsigned int keyCTRLRIGHT  = XK_Control_R;
    const unsigned int keyARROWLEFT  = XK_Left;
    const unsigned int keyARROWDOWN  = XK_Down;
    const unsigned int keyARROWRIGHT = XK_Right;
    const unsigned int keyPAD0       = XK_KP_0;
    const unsigned int keyPAD1       = XK_KP_1;
    const unsigned int keyPAD2       = XK_KP_2;
    const unsigned int keyPAD3       = XK_KP_3;
    const unsigned int keyPAD4       = XK_KP_4;
    const unsigned int keyPAD5       = XK_KP_5;
    const unsigned int keyPAD6       = XK_KP_6;
    const unsigned int keyPAD7       = XK_KP_7;
    const unsigned int keyPAD8       = XK_KP_8;
    const unsigned int keyPAD9       = XK_KP_9;
    const unsigned int keyPADADD     = XK_KP_Add;
    const unsigned int keyPADSUB     = XK_KP_Subtract;
    const unsigned int keyPADMUL     = XK_KP_Multiply;
    const unsigned int keyPADDIV     = XK_KP_Divide;

#elif cimg_display==2
    // Keycodes for Windows.
    //
    const unsigned int keyESC        = VK_ESCAPE;
    const unsigned int keyF1         = VK_F1;
    const unsigned int keyF2         = VK_F2;
    const unsigned int keyF3         = VK_F3;
    const unsigned int keyF4         = VK_F4;
    const unsigned int keyF5         = VK_F5;
    const unsigned int keyF6         = VK_F6;
    const unsigned int keyF7         = VK_F7;
    const unsigned int keyF8         = VK_F8;
    const unsigned int keyF9         = VK_F9;
    const unsigned int keyF10        = VK_F10;
    const unsigned int keyF11        = VK_F11;
    const unsigned int keyF12        = VK_F12;
    const unsigned int keyPAUSE      = VK_PAUSE;
    const unsigned int key1          = '1';
    const unsigned int key2          = '2';
    const unsigned int key3          = '3';
    const unsigned int key4          = '4';
    const unsigned int key5          = '5';
    const unsigned int key6          = '6';
    const unsigned int key7          = '7';
    const unsigned int key8          = '8';
    const unsigned int key9          = '9';
    const unsigned int key0          = '0';
    const unsigned int keyBACKSPACE  = VK_BACK;
    const unsigned int keyINSERT     = VK_INSERT;
    const unsigned int keyHOME       = VK_HOME;
    const unsigned int keyPAGEUP     = VK_PRIOR;
    const unsigned int keyTAB        = VK_TAB;
    const unsigned int keyQ          = 'Q';
    const unsigned int keyW          = 'W';
    const unsigned int keyE          = 'E';
    const unsigned int keyR          = 'R';
    const unsigned int keyT          = 'T';
    const unsigned int keyY          = 'Y';
    const unsigned int keyU          = 'U';
    const unsigned int keyI          = 'I';
    const unsigned int keyO          = 'O';
    const unsigned int keyP          = 'P';
    const unsigned int keyDELETE     = VK_DELETE;
    const unsigned int keyEND        = VK_END;
    const unsigned int keyPAGEDOWN   = VK_NEXT;
    const unsigned int keyCAPSLOCK   = VK_CAPITAL;
    const unsigned int keyA          = 'A';
    const unsigned int keyS          = 'S';
    const unsigned int keyD          = 'D';
    const unsigned int keyF          = 'F';
    const unsigned int keyG          = 'G';
    const unsigned int keyH          = 'H';
    const unsigned int keyJ          = 'J';
    const unsigned int keyK          = 'K';
    const unsigned int keyL          = 'L';
    const unsigned int keyENTER      = VK_RETURN;
    const unsigned int keySHIFTLEFT  = VK_SHIFT;
    const unsigned int keyZ          = 'Z';
    const unsigned int keyX          = 'X';
    const unsigned int keyC          = 'C';
    const unsigned int keyV          = 'V';
    const unsigned int keyB          = 'B';
    const unsigned int keyN          = 'N';
    const unsigned int keyM          = 'M';
    const unsigned int keySHIFTRIGHT = VK_SHIFT;
    const unsigned int keyARROWUP    = VK_UP;
    const unsigned int keyCTRLLEFT   = VK_CONTROL;
    const unsigned int keyAPPLEFT    = VK_LWIN;
    const unsigned int keyALT        = VK_LMENU;
    const unsigned int keySPACE      = VK_SPACE;
    const unsigned int keyALTGR      = VK_CONTROL;
    const unsigned int keyAPPRIGHT   = VK_RWIN;
    const unsigned int keyMENU       = VK_APPS;
    const unsigned int keyCTRLRIGHT  = VK_CONTROL;
    const unsigned int keyARROWLEFT  = VK_LEFT;
    const unsigned int keyARROWDOWN  = VK_DOWN;
    const unsigned int keyARROWRIGHT = VK_RIGHT;
    const unsigned int keyPAD0       = 0x60;
    const unsigned int keyPAD1       = 0x61;
    const unsigned int keyPAD2       = 0x62;
    const unsigned int keyPAD3       = 0x63;
    const unsigned int keyPAD4       = 0x64;
    const unsigned int keyPAD5       = 0x65;
    const unsigned int keyPAD6       = 0x66;
    const unsigned int keyPAD7       = 0x67;
    const unsigned int keyPAD8       = 0x68;
    const unsigned int keyPAD9       = 0x69;
    const unsigned int keyPADADD     = VK_ADD;
    const unsigned int keyPADSUB     = VK_SUBTRACT;
    const unsigned int keyPADMUL     = VK_MULTIPLY;
    const unsigned int keyPADDIV     = VK_DIVIDE;

#elif cimg_display==3
    // Keycodes for MacOSX, when using the Carbon framework.
    //
    const unsigned int keyESC        = kEscapeCharCode;
    const unsigned int keyF1         = 2U;
    const unsigned int keyF2         = 3U;
    const unsigned int keyF3         = 4U;
    const unsigned int keyF4         = 5U;
    const unsigned int keyF5         = 6U;
    const unsigned int keyF6         = 7U;
    const unsigned int keyF7         = 8U;
    const unsigned int keyF8         = 9U;
    const unsigned int keyF9         = 10U;
    const unsigned int keyF10        = 11U;
    const unsigned int keyF11        = 12U;
    const unsigned int keyF12        = 13U;
    const unsigned int keyPAUSE      = 14U;
    const unsigned int key1          = '1';
    const unsigned int key2          = '2';
    const unsigned int key3          = '3';
    const unsigned int key4          = '4';
    const unsigned int key5          = '5';
    const unsigned int key6          = '6';
    const unsigned int key7          = '7';
    const unsigned int key8          = '8';
    const unsigned int key9          = '9';
    const unsigned int key0          = '0';
    const unsigned int keyBACKSPACE  = kBackspaceCharCode;
    const unsigned int keyINSERT     = 26U;
    const unsigned int keyHOME       = kHomeCharCode;
    const unsigned int keyPAGEUP     = kPageUpCharCode;
    const unsigned int keyTAB        = kTabCharCode;
    const unsigned int keyQ          = 'q';
    const unsigned int keyW          = 'w';
    const unsigned int keyE          = 'e';
    const unsigned int keyR          = 'r';
    const unsigned int keyT          = 't';
    const unsigned int keyY          = 'y';
    const unsigned int keyU          = 'u';
    const unsigned int keyI          = 'i';
    const unsigned int keyO          = 'o';
    const unsigned int keyP          = 'p';
    const unsigned int keyDELETE     = kDeleteCharCode;
    const unsigned int keyEND        = kEndCharCode;
    const unsigned int keyPAGEDOWN   = kPageDownCharCode;
    const unsigned int keyCAPSLOCK   = 43U;
    const unsigned int keyA          = 'a';
    const unsigned int keyS          = 's';
    const unsigned int keyD          = 'd';
    const unsigned int keyF          = 'f';
    const unsigned int keyG          = 'g';
    const unsigned int keyH          = 'h';
    const unsigned int keyJ          = 'j';
    const unsigned int keyK          = 'k';
    const unsigned int keyL          = 'l';
    const unsigned int keyENTER      = kEnterCharCode;
    const unsigned int keySHIFTLEFT  = 54U; //Macintosh modifier key, emulated
    const unsigned int keyZ          = 'z';
    const unsigned int keyX          = 'x';
    const unsigned int keyC          = 'c';
    const unsigned int keyV          = 'v';
    const unsigned int keyB          = 'b';
    const unsigned int keyN          = 'n';
    const unsigned int keyM          = 'm';
    const unsigned int keySHIFTRIGHT = 62U; //Macintosh modifier key, emulated
    const unsigned int keyARROWUP    = kUpArrowCharCode;
    const unsigned int keyCTRLLEFT   = 64U; //Macintosh modifier key, emulated
    const unsigned int keyAPPLEFT    = 65U; //Macintosh modifier key, emulated
    const unsigned int keyALT        = 66U;
    const unsigned int keySPACE      = kSpaceCharCode;
    const unsigned int keyALTGR      = 67U; //Macintosh modifier key, emulated
    const unsigned int keyAPPRIGHT   = 68U; //Aliased on keyAPPLEFT
    const unsigned int keyMENU       = 69U;
    const unsigned int keyCTRLRIGHT  = 70U; //Macintosh modifier key, emulated
    const unsigned int keyARROWLEFT  = kLeftArrowCharCode;
    const unsigned int keyARROWDOWN  = kDownArrowCharCode;
    const unsigned int keyARROWRIGHT = kRightArrowCharCode;
    const unsigned int keyPAD0       = 74U;
    const unsigned int keyPAD1       = 75U;
    const unsigned int keyPAD2       = 76U;
    const unsigned int keyPAD3       = 77U;
    const unsigned int keyPAD4       = 78U;
    const unsigned int keyPAD5       = 79U;
    const unsigned int keyPAD6       = 80U;
    const unsigned int keyPAD7       = 81U;
    const unsigned int keyPAD8       = 82U;
    const unsigned int keyPAD9       = 83U;
    const unsigned int keyPADADD     = 84U;
    const unsigned int keyPADSUB     = 85U;
    const unsigned int keyPADMUL     = 86U;
    const unsigned int keyPADDIV     = 87U;

#else
    // Define unknow keycodes when no display are available.
    // (should rarely be used then !).
    //
    const unsigned int keyESC        = 1U;
    const unsigned int keyF1         = 2U;
    const unsigned int keyF2         = 3U;
    const unsigned int keyF3         = 4U;
    const unsigned int keyF4         = 5U;
    const unsigned int keyF5         = 6U;
    const unsigned int keyF6         = 7U;
    const unsigned int keyF7         = 8U;
    const unsigned int keyF8         = 9U;
    const unsigned int keyF9         = 10U;
    const unsigned int keyF10        = 11U;
    const unsigned int keyF11        = 12U;
    const unsigned int keyF12        = 13U;
    const unsigned int keyPAUSE      = 14U;
    const unsigned int key1          = 15U;
    const unsigned int key2          = 16U;
    const unsigned int key3          = 17U;
    const unsigned int key4          = 18U;
    const unsigned int key5          = 19U;
    const unsigned int key6          = 20U;
    const unsigned int key7          = 21U;
    const unsigned int key8          = 22U;
    const unsigned int key9          = 23U;
    const unsigned int key0          = 24U;
    const unsigned int keyBACKSPACE  = 25U;
    const unsigned int keyINSERT     = 26U;
    const unsigned int keyHOME       = 27U;
    const unsigned int keyPAGEUP     = 28U;
    const unsigned int keyTAB        = 29U;
    const unsigned int keyQ          = 30U;
    const unsigned int keyW          = 31U;
    const unsigned int keyE          = 32U;
    const unsigned int keyR          = 33U;
    const unsigned int keyT          = 34U;
    const unsigned int keyY          = 35U;
    const unsigned int keyU          = 36U;
    const unsigned int keyI          = 37U;
    const unsigned int keyO          = 38U;
    const unsigned int keyP          = 39U;
    const unsigned int keyDELETE     = 40U;
    const unsigned int keyEND        = 41U;
    const unsigned int keyPAGEDOWN   = 42U;
    const unsigned int keyCAPSLOCK   = 43U;
    const unsigned int keyA          = 44U;
    const unsigned int keyS          = 45U;
    const unsigned int keyD          = 46U;
    const unsigned int keyF          = 47U;
    const unsigned int keyG          = 48U;
    const unsigned int keyH          = 49U;
    const unsigned int keyJ          = 50U;
    const unsigned int keyK          = 51U;
    const unsigned int keyL          = 52U;
    const unsigned int keyENTER      = 53U;
    const unsigned int keySHIFTLEFT  = 54U;
    const unsigned int keyZ          = 55U;
    const unsigned int keyX          = 56U;
    const unsigned int keyC          = 57U;
    const unsigned int keyV          = 58U;
    const unsigned int keyB          = 59U;
    const unsigned int keyN          = 60U;
    const unsigned int keyM          = 61U;
    const unsigned int keySHIFTRIGHT = 62U;
    const unsigned int keyARROWUP    = 63U;
    const unsigned int keyCTRLLEFT   = 64U;
    const unsigned int keyAPPLEFT    = 65U;
    const unsigned int keyALT        = 66U;
    const unsigned int keySPACE      = 67U;
    const unsigned int keyALTGR      = 68U;
    const unsigned int keyAPPRIGHT   = 69U;
    const unsigned int keyMENU       = 70U;
    const unsigned int keyCTRLRIGHT  = 71U;
    const unsigned int keyARROWLEFT  = 72U;
    const unsigned int keyARROWDOWN  = 73U;
    const unsigned int keyARROWRIGHT = 74U;
    const unsigned int keyPAD0       = 75U;
    const unsigned int keyPAD1       = 76U;
    const unsigned int keyPAD2       = 77U;
    const unsigned int keyPAD3       = 78U;
    const unsigned int keyPAD4       = 79U;
    const unsigned int keyPAD5       = 80U;
    const unsigned int keyPAD6       = 81U;
    const unsigned int keyPAD7       = 82U;
    const unsigned int keyPAD8       = 83U;
    const unsigned int keyPAD9       = 84U;
    const unsigned int keyPADADD     = 85U;
    const unsigned int keyPADSUB     = 86U;
    const unsigned int keyPADMUL     = 87U;
    const unsigned int keyPADDIV     = 88U;
#endif

    const double valuePI = 3.14159265358979323846;   //!< Definition of the mathematical constant PI

    // Definition of a 7x11 font, used to return a default font for drawing text.
    const unsigned int font7x11[7*11*256/32] = {
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x80000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x90,0x0,0x7f0000,0x40000,0x0,0x0,0x4010c0a4,0x82000040,0x11848402,0x18480050,0x80430292,0x8023,0x9008000,
      0x40218140,0x4000040,0x21800402,0x18000051,0x1060500,0x8083,0x10000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x24002,0x4031,0x80000000,0x10000,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7,0x81c0400,0x40020000,0x80070080,0x40440e00,0x0,0x0,0x1,0x88180000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x200000,0x0,0x0,0x80000,0x0,0x0,0x20212140,0x5000020,0x22400204,0x240000a0,0x40848500,0x4044,0x80010038,0x20424285,0xa000020,
      0x42428204,0x2428e0a0,0x82090a14,0x4104,0x85022014,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x10240a7,0x88484040,0x40800000,0x270c3,0x87811e0e,
      0x7c70e000,0x78,0x3c23c1ef,0x1f3e1e89,0xf1c44819,0xa23cf0f3,0xc3cff120,0xc18307f4,0x4040400,0x20000,0x80080080,0x40200,0x0,
      0x40000,0x2,0x8040000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x8188,0x50603800,0xf3c00000,0x1c004003,0xc700003e,0x18180,0xc993880,0x10204081,
      0x2071ef9,0xf3e7cf9f,0x3e7c7911,0xe3c78f1e,0x7d1224,0x48906048,0x0,0x4000000,0x0,0x9000,0x0,0x0,0x2000,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x10240aa,0x14944080,0x23610000,0x68940,0x40831010,0x8891306,0x802044,0x44522208,0x90202088,0x40448819,0xb242890a,0x24011111,
      0x49448814,0x4040a00,0xe2c3c7,0x8e3f3cb9,0xc1c44216,0xee38b0f2,0xe78f9120,0xc18507e2,0x8040000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x101c207,0x88a04001,0x9c00000,0x2200a041,0x8200113a,0x8240,0x50a3110,0x2850a142,0x850c2081,0x2040204,0x8104592,0x142850a1,
      0x42cd1224,0x4888bc48,0x70e1c387,0xe3b3c70,0xe1c38e1c,0x38707171,0xc3870e1c,0x10791224,0x48906c41,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x10003ee,0x15140080,0x21810000,0x48840,0x40851020,0x8911306,0x31fd804,0x9c522408,0x90204088,0x4045081a,0xba42890a,0x24011111,
      0x49285024,0x2041b00,0x132408,0x910844c8,0x4044821b,0x7244c913,0x24041111,0x49488822,0x8040000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x28204,0x85006001,0x6a414000,0x3a004043,0xc700113a,0x8245,0x50a3a00,0x2850a142,0x850c4081,0x2040204,0x81045d2,0x142850a1,
      0x24951224,0x48852250,0x8102040,0x81054089,0x12244204,0x8108992,0x24489122,0x991224,0x4888b222,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x1000143,0xa988080,0x2147c01f,0x88840,0x83091c2c,0x1070f000,0xc000608,0xa48bc408,0x9e3c46f8,0x40460816,0xaa42f10b,0xc3811111,
      0x35102044,0x1041100,0xf22408,0x9f084488,0x40470212,0x62448912,0x6041111,0x55308846,0x8061c80,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x1028704,0x8f805801,0x4be28fdf,0x220001f0,0x111a,0x60000182,0x82c5c710,0x44891224,0x489640f1,0xe3c78204,0x810e552,0x142850a1,
      0x18a51224,0x48822250,0x78f1e3c7,0x8f1f40f9,0xf3e7c204,0x8108912,0x24489122,0x7ea91224,0x4888a222,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x10007e2,0x85648080,0x20010000,0x88841,0x8f8232,0x20881000,0xc1fc610,0xbefa2408,0x90204288,0x40450816,0xa642810a,0x4041110a,
      0x36282084,0x1042080,0x1122408,0x90084488,0x40450212,0x62448912,0x184110a,0x55305082,0x8042700,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x1028207,0x82004801,0x68050040,0x1c000040,0x110a,0x60000001,0x45484d10,0x7cf9f3e7,0xcf944081,0x2040204,0x8104532,0x142850a1,
      0x18a51224,0x48822248,0x89122448,0x91244081,0x2040204,0x8108912,0x24489122,0xc91224,0x48852214,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x282,
      0x89630080,0x20010c00,0x30108842,0x810222,0x20882306,0x3001800,0x408a2208,0x90202288,0x40448814,0xa642810a,0x2041110a,0x26442104,
      0x840000,0x1122408,0x90084488,0x40448212,0x62448912,0x84130a,0x36485102,0x8040000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x101c208,0x4f802801,
      0x8028040,0x40,0x130a,0x2,0x85e897a0,0x44891224,0x489c2081,0x2040204,0x8104532,0x142850a1,0x24cd1224,0x48823c44,0x89122448,
      0x91244081,0x2040204,0x8108912,0x24489122,0xc93264,0xc9852214,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x100028f,0x109f0080,0x20010c00,
      0x303071f3,0xc7011c1c,0x4071c306,0x802010,0x3907c1ef,0x1f201e89,0xf3844f90,0xa23c80f2,0x17810e04,0x228223f4,0x840000,0xfbc3c7,
      0x8f083c88,0x40444212,0x6238f0f2,0x7039d04,0x228423e2,0x8040000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1008780,0x2201800,0xf0014000,0x1f0,
      0x1d0a,0x5,0x851e140,0x83060c18,0x30671ef9,0xf3e7cf9f,0x3e7c7911,0xe3c78f1e,0x42f8e1c3,0x8702205c,0x7cf9f3e7,0xcf9b3c78,0xf1e3c204,
      0x8107111,0xc3870e1c,0x10f1d3a7,0x4e823c08,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x2,0x40,0x40000400,0x200000,0x0,0x2,0x0,0x0,0x0,0x0,0x18,
      0x0,0x4,0x44007f,0x0,0x400,0x400000,0x8010,0x0,0x6002,0x8040000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1000000,0x200800,0x0,0x0,0x100a,
      0x400000,0x44,0x0,0x400,0x0,0x0,0x0,0x0,0x0,0x0,0x800,0x0,0x0,0x0,0x0,0x62018,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x31,0x80000800,
      0x400000,0x0,0x4,0x0,0x0,0x0,0x0,0xc,0x0,0x7,0x3c0000,0x0,0x3800,0x3800000,0x8010,0x0,0x1c001,0x881c0000,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x207000,0x0,0x0,0x100a,0xc00000,0x3c,0x0,0xc00,0x0,0x0,0x0,0x0,0x0,0x0,0x1800,0x0,0x0,0x0,0x0,0x1c2070
    };

    // Definition of a 10x13 font (used in dialog boxes).
    const unsigned int font10x13[256*10*13/32] = {
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x80100c0,
      0x68000300,0x801,0xc00010,0x100c000,0x68100,0x100c0680,0x2,0x403000,0x1000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xfc,0x0,0x0,0x0,0x0,0x0,0x4020120,
      0x58120480,0x402,0x1205008,0x2012050,0x58080,0x20120581,0x40000001,0x804812,0x2000000,0x0,0x300,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x140,0x80000,0x200402,0x800000,0x10,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x7010,0x7000000,0x8000200,0x20000,0xc0002000,0x8008,0x0,0x0,0x0,0x0,0x808,0x4000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x80000000,0x0,0x0,0x0,0x40000,0x0,0x0,0x0,0x0,0x480,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x70,0x80100c0,0x68000480,0x1001,
      0xc00010,0x1018000,0x68100,0x100c0680,0x4,0x403000,0x1020000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20140,0x28081883,0x200801,
      0x2a00000,0x10,0x1c0201c0,0x70040f80,0xc0f81c07,0x0,0x70,0x3e0303c0,0x3c3c0f83,0xe03c2107,0xe08810,0x18c31070,0x3c0703c0,
      0x783e0842,0x22222208,0x83e04010,0x1008000,0x4000200,0x20001,0x2002,0x408008,0x0,0x0,0x100000,0x0,0x1008,0x2000000,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20080,0x38000880,0x8078140f,0x81c00000,0x3e000,0xc020180,0x60080001,0xe0000002,0xc00042,0x108e2010,
      0xc0300c0,0x300c0303,0xf83c3e0f,0x83e0f81c,0x701c070,0x3c0c41c0,0x701c0701,0xc0001d08,0x42108421,0x8820088,0x4020120,0x58140480,
      0x802,0x1205008,0x3014050,0xc058080,0x20120581,0x40000002,0x804814,0x2020050,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20140,
      0x281e2484,0x80200801,0x1c02000,0x10,0x22060220,0x880c0801,0x82208,0x80000001,0x20008,0x41030220,0x40220802,0x402102,0x209010,
      0x18c31088,0x22088220,0x80080842,0x22222208,0x80204010,0x1014000,0x200,0x20001,0x2000,0x8008,0x0,0x0,0x100000,0x0,0x1008,
      0x2000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x80,0x40000500,0x80800010,0x40200000,0x41000,0x12020040,0x10000003,0xa0000006,
      0x12000c4,0x31014000,0xc0300c0,0x300c0302,0x80402008,0x2008008,0x2008020,0x220c4220,0x88220882,0x20002208,0x42108421,0x8820088,
      0x0,0x300,0x0,0x0,0x0,0x14000000,0x0,0x200200,0x0,0x20000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20000,0xfc282504,0x80001000,
      0x82a02000,0x20,0x22020020,0x8140802,0x102208,0x80801006,0x18008,0x9c848220,0x80210802,0x802102,0x20a010,0x15429104,0x22104220,
      0x80080842,0x22221405,0x404008,0x1022000,0x703c0,0x381e0701,0xc0783c02,0xc09008,0x1d83c070,0x3c078140,0x381c0882,0x21242208,
      0x81e01008,0x2000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x201e0,0x40220500,0x80800027,0x20e02800,0x9c800,0x12020040,
      0x20000883,0xa0200002,0x120a044,0x11064010,0x12048120,0x48120484,0x80802008,0x2008008,0x2008020,0x210a4411,0x4411044,0x10884508,
      0x42108421,0x503c0b0,0x1c0701c0,0x701c0707,0x70381c07,0x1c07008,0x2008020,0x20f01c0,0x701c0701,0xc0201c08,0x82208822,0x883c088,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20000,0x50281903,0x20001000,0x80802000,0x20,0x22020040,0x30240f03,0xc0101c08,0x80801018,
      0x1fc06010,0xa48483c0,0x80210f03,0xe0803f02,0x20c010,0x15429104,0x22104220,0x70080841,0x41540805,0x804008,0x1041000,0x8220,
      0x40220881,0x882202,0x40a008,0x12422088,0x22088180,0x40100882,0x21241408,0x80201008,0x2031000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x20280,0x401c0200,0x700028,0x21205000,0x92800,0xc1fc080,0x10000883,0xa0200002,0x1205049,0x12c19010,0x12048120,0x48120484,
      0xf0803c0f,0x3c0f008,0x2008020,0x790a4411,0x4411044,0x10504908,0x42108421,0x5022088,0x2008020,0x8020080,0x88402208,0x82208808,
      0x2008020,0x1e088220,0x88220882,0x20002608,0x82208822,0x8822088,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20000,0x501c0264,
      0xa0001000,0x8001fc00,0x7000020,0x22020080,0x83e0082,0x20202207,0x80000020,0x1020,0xa4848220,0x80210802,0x9c2102,0x20c010,
      0x12425104,0x3c1043c0,0x8080841,0x41540802,0x804008,0x1000000,0x78220,0x40220f81,0x882202,0x40c008,0x12422088,0x22088100,
      0x60100881,0x41540805,0x406008,0x1849000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20280,0xf0140200,0x880028,0x20e0a03f,0x709c800,
      0x201c0,0x60000881,0xa0000007,0xc0284b,0x122eb020,0x12048120,0x48120487,0x80802008,0x2008008,0x2008020,0x21094411,0x4411044,
      0x10204908,0x42108421,0x2022088,0x1e0781e0,0x781e0787,0xf8403e0f,0x83e0f808,0x2008020,0x22088220,0x88220882,0x21fc2a08,0x82208822,
      0x5022050,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20001,0xf80a0294,0x40001000,0x80002000,0x20,0x22020100,0x8040082,0x20202200,
      0x80000018,0x1fc06020,0xa48fc220,0x80210802,0x842102,0x20a010,0x12425104,0x20104240,0x8080841,0x41541402,0x1004008,0x1000000,
      0x88220,0x40220801,0x882202,0x40a008,0x12422088,0x22088100,0x18100881,0x41540805,0x801008,0x2046000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x20280,0x401c0f80,0x80880028,0x20005001,0x94800,0x20000,0x880,0xa0000000,0x5015,0x4215040,0x3f0fc3f0,0xfc3f0fc8,
      0x80802008,0x2008008,0x2008020,0x21094411,0x4411044,0x10505108,0x42108421,0x203c088,0x22088220,0x88220888,0x80402008,0x2008008,
      0x2008020,0x22088220,0x88220882,0x20002a08,0x82208822,0x5022050,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xa00a0494,0x60001000,
      0x80002004,0x8020,0x22020200,0x88040882,0x20402201,0x801006,0x18000,0x9f084220,0x40220802,0x442102,0x209010,0x10423088,0x20088220,
      0x8080840,0x80882202,0x2004008,0x1000000,0x88220,0x40220881,0x882202,0x409008,0x12422088,0x22088100,0x8100880,0x80881402,
      0x1001008,0x2000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20280,0x40220200,0x80700027,0x20002801,0x92800,0x1fc000,0x980,
      0xa0000000,0xa017,0x84417840,0x21084210,0x84210848,0x80402008,0x2008008,0x2008020,0x2208c220,0x88220882,0x20882208,0x42108421,
      0x2020088,0x22088220,0x88220888,0xc8402208,0x82208808,0x2008020,0x22088220,0x88220882,0x20203208,0x82208822,0x2022020,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20000,0xa03c0463,0x90000801,0x2004,0x8040,0x1c0703e0,0x70040701,0xc0401c06,0x801001,0x20020,
      0x400843c0,0x3c3c0f82,0x3c2107,0x1c0881e,0x10423070,0x20070210,0xf0080780,0x80882202,0x3e04004,0x1000000,0x783c0,0x381e0701,
      0x782202,0x408808,0x12422070,0x3c078100,0x700c0780,0x80882202,0x1e01008,0x2000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x201e0,
      0xf8000200,0x80080010,0x40000001,0x41000,0x0,0xe80,0xa0000000,0x21,0x8e21038,0x21084210,0x84210848,0xf83c3e0f,0x83e0f81c,
      0x701c070,0x3c08c1c0,0x701c0701,0xc0005c07,0x81e0781e,0x20200b0,0x1e0781e0,0x781e0787,0x30381c07,0x1c07008,0x2008020,0x1c0881c0,
      0x701c0701,0xc0201c07,0x81e0781e,0x203c020,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x80000,0x801,0x4,0x40,0x0,0x0,0x0,0x1000,
      0x0,0x3c000000,0x0,0x0,0x0,0x0,0x10000,0x0,0x0,0x4004,0x1000000,0x0,0x0,0x80000,0x400000,0x0,0x20008000,0x0,0x4,0x1008,0x2000000,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x80,0x0,0x8008000f,0x80000000,0x3e000,0x0,0x800,0xa0000400,0x0,0x0,0x0,0x0,0x80000,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x100000,0x0,0x0,0x0,0x0,0x2000,0x0,0x4020040,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x80000,
      0x402,0x8,0x40,0x0,0x0,0x0,0x2000,0x0,0x0,0x0,0x0,0x0,0x0,0xc000,0x0,0x0,0x7004,0x70000fc,0x0,0x0,0x700000,0x800000,0x0,0x20008000,
      0x0,0x4,0x808,0x4000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x80,0x0,0x80f00000,0x0,0x0,0x0,0x800,0xa0001800,0x0,0x0,0x0,0x0,
      0x300000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x600000,0x0,0x0,0x0,0x0,0x0,0x0,0x4020040
    };

    // Definition of a 8x17 font.
    const unsigned int font8x17[8*17*256/32] = {
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x2400,0x2400,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20081834,0x1c0000,0x20081800,0x20081800,0x342008,
      0x18340000,0x200818,0x80000,0x0,0x180000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x4200000,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c,0x380000,0x4000,0x2000c00,0x40100840,0x70000000,0x0,0x0,0x1c,0x10700000,0x7,0x0,
      0x1800,0x1800,0x0,0x0,0x0,0x14,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1010242c,0x14140000,0x10102414,0x10102414,0x2c1010,0x242c1400,
      0x101024,0x14100038,0x0,0x240000,0x0,0x0,0x30000000,0x0,0x0,0x4000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x12,0x0,0x8100000,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x10,0x80000,0x10004000,0x2001000,0x40000040,0x10000000,0x0,0x0,0x10,0x10100000,0x4,
      0x0,0x18000000,0x0,0x0,0x0,0x34002400,0x2400,0x0,0x0,0x0,0x3c,0x0,0x8000000,0x0,0x60607800,0x0,0x140000,0x0,0x0,0x0,0x0,0x0,
      0x44,0x10081834,0x240000,0x10081800,0x10081800,0x1c341008,0x18340000,0x100818,0x84000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x102812,
      0x8601c10,0x8100800,0x2,0x1c383e3e,0x67e1e7f,0x3e3c0000,0x38,0x1e087e1e,0x7c7f7f1e,0x417c1c42,0x4063611c,0x7e1c7e3e,0xfe414181,
      0x63827f10,0x40081000,0x8004000,0x2001000,0x40000040,0x10000000,0x0,0x10000000,0x10,0x10100000,0x3c000008,0x0,0x24003e00,
      0x3f007f00,0x0,0x0,0x2ce91800,0x1882,0x10101c,0xc2103c,0x143c3c00,0x3c00,0x18003c3c,0x10001f00,0x181c00,0x20200810,0x8080808,
      0x8083e1e,0x7f7f7f7f,0x7c7c7c7c,0x7c611c1c,0x1c1c1c00,0x1e414141,0x41824044,0x810242c,0x14180000,0x8102414,0x8102414,0x382c0810,
      0x242c1400,0x81024,0x14104014,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x102816,0x3e902010,0x10084910,0x4,0x22084343,0xa402102,0x41620000,
      0x44,0x33144121,0x42404021,0x41100444,0x40636122,0x43224361,0x10416381,0x22440310,0x20082800,0x4000,0x2001000,0x40000040,
      0x10000000,0x0,0x10000000,0x10,0x10100000,0x24000008,0x0,0x606100,0x68000300,0x8106c,0x34000000,0x4f0000,0x44,0x101020,0x441040,
      0x420200,0x4200,0x24000404,0x7d00,0x82200,0x20203010,0x14141414,0x14082821,0x40404040,0x10101010,0x42612222,0x22222200,0x23414141,
      0x41447e48,0x0,0x0,0x0,0x0,0x4000000,0x18,0x0,0x4000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x10287f,0x49902010,0x10083e10,0x4,0x41080101,
      0x1a404002,0x41411818,0x1004004,0x21144140,0x41404040,0x41100448,0x40555141,0x41414140,0x10412281,0x14280610,0x20084400,0x1c7c1c,
      0x3e3c7c3a,0x5c703844,0x107f5c3c,0x7c3e3c3c,0x7e424281,0x66427e10,0x10100000,0x40100008,0x1010,0xa04000,0x48100610,0x100c3024,
      0x24000000,0x4f3c00,0x2c107e28,0x3820,0x42281060,0x9d1e12,0xbd00,0x24100818,0x427d00,0x82248,0x20200800,0x14141414,0x14142840,
      0x40404040,0x10101010,0x41514141,0x41414142,0x43414141,0x41284350,0x1c1c1c1c,0x1c1c6c1c,0x3c3c3c3c,0x70707070,0x3c5c3c3c,
      0x3c3c3c18,0x3e424242,0x42427c42,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x102824,0x48623010,0x10081c10,0x8,0x41080103,0x127c5e04,
      0x41411818,0xe7f3808,0x4f144140,0x41404040,0x41100450,0x40555141,0x41414160,0x1041225a,0x1c280410,0x1008c600,0x226622,0x66661066,
      0x62100848,0x10496266,0x66663242,0x10426681,0x24220260,0x100c0000,0xf8280008,0x1010,0x606000,0x48280428,0x28042014,0x48000000,
      0x494200,0x52280228,0x105420,0x3cee1058,0xa12236,0xa500,0x18101004,0x427d00,0x8226c,0x76767e10,0x14141414,0x14142840,0x40404040,
      0x10101010,0x41514141,0x41414124,0x45414141,0x41284150,0x22222222,0x22221222,0x66666666,0x10101010,0x66626666,0x66666600,
      0x66424242,0x42226622,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x100024,0x381c4900,0x10086bfe,0x8,0x4908021c,0x22036304,0x3e630000,
      0x70000710,0x51227e40,0x417f7f43,0x7f100470,0x40554941,0x43417e3e,0x1041225a,0x8100810,0x10080000,0x24240,0x42421042,0x42100850,
      0x10494242,0x42422040,0x1042245a,0x18240410,0x10103900,0x407c003e,0x1818,0x1c3e10,0x4f7c087c,0x7c002010,0x48000000,0x4008,
      0x527c0410,0x105078,0x2410104c,0xa13e6c,0x7f00b900,0xfe3c3c,0x421d18,0x1c1c36,0x38383810,0x22222222,0x22144e40,0x7f7f7f7f,
      0x10101010,0xf1494141,0x41414118,0x49414141,0x4110435c,0x2020202,0x2021240,0x42424242,0x10101010,0x42424242,0x424242ff,0x4e424242,
      0x42244224,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1000fe,0xe664d00,0x10080810,0x380010,0x41080c03,0x42014108,0x633d0000,0x70000710,
      0x51224140,0x41404041,0x41100448,0x40494541,0x7e414203,0x1041145a,0x14101010,0x10080000,0x3e4240,0x427e1042,0x42100870,0x10494242,
      0x4242203c,0x1042245a,0x18241810,0x10104600,0xf8f60008,0x1010,0x600320,0x48f610f6,0xf6000000,0x187eff,0x3c04,0x5ef61810,0x105020,
      0x24fe0064,0x9d006c,0x138ad00,0x100000,0x420518,0x36,0xc0c0c020,0x22222222,0x22224840,0x40404040,0x10101010,0x41454141,0x41414118,
      0x51414141,0x41107e46,0x3e3e3e3e,0x3e3e7e40,0x7e7e7e7e,0x10101010,0x42424242,0x42424200,0x5a424242,0x42244224,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x28,0x9094500,0x10080010,0x10,0x41081801,0x7f014118,0x41010000,0xe7f3800,0x513e4140,0x41404041,0x41100444,
      0x40414541,0x40414101,0x10411466,0x36103010,0x8080000,0x424240,0x42401042,0x42100848,0x10494242,0x42422002,0x10423c5a,0x18142010,
      0x10100000,0x407c0010,0x1010,0x260140,0x487c307c,0x7c000000,0x180000,0x202,0x507c2010,0x105020,0x3c10003c,0x423e36,0x1004200,
      0x100000,0x420500,0x3e6c,0x41e0440,0x3e3e3e3e,0x3e3e7840,0x40404040,0x10101010,0x41454141,0x41414124,0x61414141,0x41104042,
      0x42424242,0x42425040,0x40404040,0x10101010,0x42424242,0x42424218,0x72424242,0x42144214,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x100048,
      0x49096200,0x8100010,0x18001810,0x22082043,0x2432310,0x61421818,0x1004010,0x4f634121,0x42404021,0x41104444,0x40414322,0x40234143,
      0x10411466,0x22106010,0x8080000,0x466622,0x66621066,0x42100844,0x10494266,0x66662042,0x10461824,0x24184010,0x10100000,0x24381010,
      0x34001018,0xda4320,0x68386038,0x38000000,0x0,0x4204,0x50384010,0x105420,0x4210100c,0x3c0012,0x3c00,0x0,0x460500,0x48,0xc020c44,
      0x63636363,0x63228821,0x40404040,0x10101010,0x42432222,0x22222242,0x62414141,0x41104042,0x46464646,0x46465022,0x62626262,
      0x10101010,0x66426666,0x66666618,0x66464646,0x46186618,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x100048,0x3e063d00,0x8100000,0x18001820,
      0x1c3e7f3e,0x23c1e20,0x3e3c1818,0x10,0x20417e1e,0x7c7f401e,0x417c3842,0x7f41431c,0x401e40be,0x103e0866,0x41107f10,0x4080000,
      0x3a5c1c,0x3a3c103a,0x427c0842,0xe49423c,0x7c3e203c,0xe3a1824,0x66087e10,0x10100000,0x3c103010,0x245a1010,0x5a3e10,0x3f107f10,
      0x10000000,0x0,0x3c08,0x2e107e10,0x1038fc,0x101004,0x0,0x0,0xfe0000,0x7f0500,0x0,0x14041438,0x41414141,0x41418e1e,0x7f7f7f7f,
      0x7c7c7c7c,0x7c431c1c,0x1c1c1c00,0xbc3e3e3e,0x3e10405c,0x3a3a3a3a,0x3a3a6e1c,0x3c3c3c3c,0x7c7c7c7c,0x3c423c3c,0x3c3c3c00,
      0x7c3a3a3a,0x3a087c08,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x8000000,0x4200000,0x10000020,0x0,0x0,0x10,0x0,0x30000000,0x0,
      0x0,0x0,0x60000,0x0,0x1c,0x4380000,0x0,0x2,0x800,0x0,0x40020000,0x0,0x8000c,0x10600000,0x2010,0x48000000,0x240000,0x0,0x0,
      0x0,0x0,0x0,0x1000,0x1078,0x0,0x0,0x0,0x400500,0x0,0x1e081e00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x84008,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x8000000,0x0,0x20000040,0x0,0x0,0x20,0x0,0x1e000000,0x0,0x0,0x0,0x20000,0x0,
      0x0,0x2000000,0x0,0x26,0x800,0x0,0x40020000,0x0,0x100000,0x10000000,0x2030,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1000,0x1000,0x0,
      0x0,0x0,0x400000,0x8000000,0x41e0400,0x0,0x4,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x4,0x0,0x0,0x0,0x0,0x0,0x104010,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xfe,0x0,0x1c,0x7000,0x0,0x40020000,0x0,0x300000,
      0x0,0xe0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1000,0x0,0x0,0x0,0x400000,0x38000000,0x0,0x0,0x1c,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x1c,0x0,0x0,0x0,0x0,0x0,0x304030,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0 };

    // Definition of a 10x19 font.
    const unsigned int font10x19[10*19*256/32] = {
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3600000,0x36000,0x0,0x0,0x0,0x0,0x6c,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x180181c0,0xe81b0300,0x1801,0x81c06c18,0x181c06c,0xe8180,0x181c0e81,0xb0000006,0x60701b,0x1800000,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c00000,0x1c000,0x0,0x0,0x0,0x0,0x6c,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0xc030360,0xb81b0480,0xc03,0x3606c0c,0x303606c,0xb80c0,0x30360b81,0xb0000003,0xc0d81b,0x3000000,0x0,
      0x300,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x30000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x800,0x0,0x0,0x0,0x0,0x0,0x2200000,
      0x22000,0x0,0x0,0x0,0x0,0x0,0x0,0x30000,0x0,0xe0,0x38078000,0x0,0x480,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3000c080,0x480,0x3000,
      0xc0800030,0xc08000,0x300,0xc080000,0xc,0x302000,0xc00000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20120,0x41c01,0xe020060c,
      0x800000,0x4,0x1e0703e0,0xf8060fc1,0xe1fe1e07,0x80000000,0x78,0x307e0,0x3c7c1fe7,0xf83c408f,0x80f10440,0x18660878,0x7e0787e0,
      0x78ff9024,0xa0140a0,0x27f83840,0x700e000,0x18000400,0x8000,0x70004002,0x410078,0x0,0x0,0x0,0x0,0x1808,0xc000000,0xf000000,
      0xe000000,0x1400,0x1e0001f,0x8007f800,0x0,0x0,0x3a3b,0x61400000,0x14202,0x20000,0x38002020,0x3c1b00,0x3e00000,0xf8,0x1c0001c0,
      0x78060001,0xf800000e,0x1e00020,0x8004020,0xc0300c0,0x300c0301,0xf83c7f9f,0xe7f9fe3e,0xf83e0f8,0x7c1821e0,0x781e0781,0xe0001f10,
      0x24090240,0xa02400f8,0x18018140,0xe81b0480,0x1801,0x81406c18,0x181406c,0x190e8180,0x18140e81,0xb0000006,0x60501b,0x184006c,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20120,0x26042202,0x200c06,0x800000,0x8,0x210d0611,0x40e0803,0x10026188,0x40000000,
      0x8c,0xf030418,0xc6431004,0xc64082,0x110840,0x18660884,0x41084410,0x8c081024,0xa012110,0x40082020,0x101b000,0xc000400,0x8000,
      0x80004002,0x410008,0x0,0x0,0x100000,0x0,0x2008,0x2000000,0x18800000,0x10000000,0x2200,0x2300024,0x800,0x0,0x0,0x2e13,0x60800000,
      0x8104,0x20040,0x64001040,0x80401b07,0x80100000,0x1e000,0x22000020,0x40c0003,0xc8000002,0x3300020,0x8004020,0xc0300c0,0x300c0301,
      0x40c64010,0x4010008,0x2008020,0x43182210,0x84210842,0x10002190,0x24090240,0x9044018c,0xc030220,0xb81b0300,0xc03,0x2206c0c,
      0x302206c,0x1e0b80c0,0x30220b81,0xb0000003,0xc0881b,0x304006c,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20120,0x241f2202,
      0x200802,0x4900000,0x8,0x21010408,0x20a0802,0x44090,0x20000000,0x4,0x11878408,0x80411004,0x804082,0x111040,0x1ce50986,0x40986409,
      0x81022,0x12012108,0x80102020,0x1031800,0x400,0x8000,0x80004000,0x10008,0x0,0x0,0x100000,0x0,0x2008,0x2000000,0x10000000,
      0x10000000,0x18,0x4000044,0x1000,0x30180,0xd81b0000,0x13,0xe0000000,0x88,0x40,0x400018c0,0x80400018,0x61f00000,0x61800,0x22020020,
      0x4000007,0xc8000002,0x2100020,0x8038000,0x1e0781e0,0x781e0301,0x40804010,0x4010008,0x2008020,0x41142619,0x86619866,0x18002190,
      0x24090240,0x8887e104,0x0,0x0,0x0,0x0,0x0,0x2000000,0x0,0x0,0x0,0x40000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20120,0x2434a202,
      0x200802,0x3e00000,0x10,0x40810008,0x21a0804,0x44090,0x20000000,0x80040004,0x20848409,0x409004,0x1004082,0x112040,0x14a50902,
      0x40902409,0x81022,0x11321208,0x80202010,0x1060c00,0x7c5e0,0x781e8783,0xf07a5f0e,0x1c10808,0xfc5f078,0x5e07a170,0x7c7e1024,
      0xa016190,0x27f82008,0x2000000,0x20000000,0x10000000,0x80200024,0x4000044,0x2000,0x18180,0xc8320000,0x12,0xa1f00037,0x7f888,
      0x1e0,0x40410880,0x80600017,0xa2100000,0x5e800,0x22020040,0x38001027,0xc8000002,0x2100020,0x8004020,0x12048120,0x48120482,
      0x41004010,0x4010008,0x2008020,0x40942409,0x2409024,0x9044390,0x24090240,0x88841918,0x1f07c1f0,0x7c1f07c3,0x70781e07,0x81e07838,
      0xe0380e0,0x1f17c1e0,0x781e0781,0xe0001f90,0x24090240,0x9025e102,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20001,0xff241c41,
      0x1001,0x1c02000,0x10,0x40810008,0x6120f85,0xe0086190,0x20c03007,0x8007800c,0x27848419,0x409004,0x1004082,0x114040,0x14a48902,
      0x40902409,0x81022,0x11321205,0x602010,0x1000000,0x86610,0x84218840,0x80866182,0x411008,0x9261884,0x61086189,0x82101022,0x12012108,
      0x40082008,0x2000000,0x20030000,0x20000000,0x80200024,0x4000044,0x3006030,0xc018100,0x4c260000,0x12,0x26080048,0x83000850,
      0x20250,0x403e0500,0x8078002c,0x12302200,0x92400,0x1c0200c0,0x4001027,0xc8000002,0x3308820,0x8004020,0x12048120,0x48120482,
      0x41004010,0x4010008,0x2008020,0x40922409,0x2409024,0x8884690,0x24090240,0x85040920,0x21886218,0x86218860,0x88842108,0x42108408,
      0x2008020,0x21186210,0x84210842,0x10302190,0x24090240,0x88461084,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20000,0x4c240182,
      0x80001001,0x6b02000,0x20,0x4c810010,0x78220846,0x10081e10,0x20c0301c,0x1fe0e018,0x4d8487e1,0x409fe7,0xf9007f82,0x11a040,
      0x13248902,0x41102418,0xe0081022,0x11320c05,0x402008,0x1000000,0x2409,0x409020,0x81024082,0x412008,0x9240902,0x40902101,0x101022,
      0x11321208,0x40102008,0x2000000,0x7e0c8000,0xfc000003,0xf0fc0018,0x43802047,0x8c8040c8,0x32008300,0x44240000,0x0,0x4000048,
      0x8c801050,0x20440,0x40221dc0,0x808c0028,0x11d0667f,0x8009c400,0x1fc180,0x4001023,0xc8300002,0x1e0ccfb,0x3ec7b020,0x12048120,
      0x48120482,0x79007f9f,0xe7f9fe08,0x2008020,0xf0922409,0x2409024,0x8504490,0x24090240,0x85040920,0x802008,0x2008020,0x89004090,
      0x24090208,0x2008020,0x40902409,0x2409024,0x8304390,0x24090240,0x88440884,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20000,
      0x481c0606,0xc8001001,0x802000,0x20,0x4c810020,0x4220024,0x8102108,0x60000070,0x3820,0x48884419,0x409004,0x10e4082,0x112040,
      0x13244902,0x7e1027e0,0x3c081021,0x21320c02,0x802008,0x1000000,0x7e409,0x409020,0x81024082,0x414008,0x9240902,0x40902101,
      0x80101022,0x11320c08,0x40202008,0x2038800,0x200bc000,0x20000000,0x80200003,0x80f04044,0xbc080bc,0x2f000200,0x0,0x0,0x6001048,
      0x8bc02020,0x20441,0xf8220200,0x80820028,0x1000cc00,0x80094400,0x201e0,0x78001021,0xc830000f,0x8000663c,0xf03c0c0,0x21084210,
      0x84210846,0x41004010,0x4010008,0x2008020,0x40912409,0x2409024,0x8204890,0x24090240,0x82040930,0x1f87e1f8,0x7e1f87e0,0x89004090,
      0x24090208,0x2008020,0x40902409,0x2409024,0x8004690,0x24090240,0x88440884,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20000,
      0x480719c4,0x48001001,0x81fc00,0x7800020,0x40810040,0x2420024,0x8104087,0xa0000070,0x3820,0x48884409,0x409004,0x1024082,0x111040,
      0x13244902,0x40102410,0x2081021,0x214a1202,0x1802008,0x1000000,0x182409,0x409fe0,0x81024082,0x41a008,0x9240902,0x40902100,
      0xf8101021,0x214a0c04,0x80c0c008,0x1847000,0x7c1ee000,0x20000000,0x8020000c,0x8c044,0x1ee181ee,0x7b800000,0x707,0xf3ff0000,
      0x3e0084f,0x9ee0c020,0x20440,0x40221fc0,0xc2002c,0x13f11000,0x87892400,0x20000,0x1020,0x48000000,0x3f011c6,0x31cc6180,0x21084210,
      0x84210844,0x41004010,0x4010008,0x2008020,0x40912409,0x2409024,0x8505090,0x24090240,0x8204191c,0x60982609,0x82609823,0xf9007f9f,
      0xe7f9fe08,0x2008020,0x40902409,0x2409024,0x9fe4c90,0x24090240,0x84840848,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3,0xfe048224,
      0x28001001,0x2000,0x40,0x40810080,0x27f8024,0x8104080,0x2000001c,0x1fe0e020,0x488fc409,0x409004,0x1024082,0x110840,0x10242902,
      0x40102408,0x2081021,0x214a1202,0x1002004,0x1000000,0x102409,0x409000,0x81024082,0x411008,0x9240902,0x40902100,0x6101021,
      0x214a0c04,0x81002008,0x2000000,0x201dc000,0x20000000,0x80200000,0x98044,0x1dc101dc,0x77000000,0x700,0x0,0x180448,0x1dc10020,
      0x20440,0x403e0200,0x620017,0xa000cc00,0x80052800,0x20000,0x1020,0x48000000,0x6606,0x206100,0x3f0fc3f0,0xfc3f0fc7,0xc1004010,
      0x4010008,0x2008020,0x4090a409,0x2409024,0x8886090,0x24090240,0x8207e106,0x40902409,0x2409024,0x81004010,0x4010008,0x2008020,
      0x40902409,0x2409024,0x8005890,0x24090240,0x84840848,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x98048224,0x30001001,0x2000,
      0x40,0x21010100,0x2020024,0x8204080,0x40000007,0x80078000,0x48884408,0x80411004,0x824082,0x110840,0x10242986,0x40086409,0x2081021,
      0xe14a2102,0x2002004,0x1000000,0x106409,0x409000,0x81024082,0x410808,0x9240902,0x40902100,0x2101021,0x214a1202,0x82002008,
      0x2000000,0x300f8000,0x20000000,0x80fc001d,0xe4088044,0xf8200f8,0x3e000000,0x300,0x0,0x80c48,0xf820020,0x20640,0x40410200,
      0x803c0018,0x60006600,0x61800,0x0,0x1020,0x48000000,0xcc0a,0x20a100,0x21084210,0x84210844,0x40804010,0x4010008,0x2008020,
      0x4110a619,0x86619866,0x19046110,0x24090240,0x82040102,0x41906419,0x6419064,0x81004010,0x4010008,0x2008020,0x40902409,0x2409024,
      0x8307090,0x24090240,0x82840828,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20000,0x90248222,0x30000802,0x200c,0xc080,0x21010301,
      0x4021042,0x10202108,0xc0c03000,0x80040020,0x4d902418,0xc6431004,0xc24082,0x6210440,0x10241884,0x40084409,0x86080840,0xc0842102,
      0x4002002,0x1000000,0x18e610,0x84218820,0x80864082,0x410408,0x9240884,0x61086101,0x6101860,0xc0842103,0x4002008,0x2000000,
      0x10850180,0x20330000,0x80200013,0x26184024,0x5040050,0x14000000,0x0,0x0,0x4180848,0x85040020,0x20350,0x40000200,0x800c0007,
      0x80002200,0x1e000,0x0,0x1860,0x48000000,0x880a,0x40a188,0x40902409,0x2409028,0x40c64010,0x4010008,0x2008020,0x43106210,0x84210842,
      0x10006108,0x42108421,0x2040102,0x6398e639,0x8e6398e4,0x88842088,0x22088208,0x2008020,0x21102210,0x84210842,0x10306118,0x66198661,
      0x83061030,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20001,0x901f01c1,0xe8000802,0xc,0xc080,0x1e07c7f8,0xf8020f81,0xe0401e07,
      0x80c03000,0x20,0x279027e0,0x3c7c1fe4,0x3c408f,0x83c1027f,0x90241878,0x4007c404,0xf8080780,0xc0844082,0x7f82002,0x1000000,
      0xfa5e0,0x781e87c0,0x807a409f,0xc0410207,0x9240878,0x5e07a100,0xf80e0fa0,0xc0846183,0x7f82008,0x2000000,0xf020100,0x40321360,
      0x80200014,0xa3e0201f,0x8207f820,0x8000000,0x0,0x0,0x3e01037,0x207f820,0x201e1,0xfc000200,0x80040000,0x0,0x0,0x1fc000,0x17b0,
      0x48000000,0x12,0xc120f0,0x40902409,0x2409028,0x783c7f9f,0xe7f9fe3e,0xf83e0f8,0x7c1061e0,0x781e0781,0xe000be07,0x81e0781e,
      0x204017c,0x3e8fa3e8,0xfa3e8fa3,0x70781f07,0xc1f07c7f,0x1fc7f1fc,0x1e1021e0,0x781e0781,0xe0007e0f,0xa3e8fa3e,0x8305e030,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x40000,0xc06,0xc,0x100,0x0,0x0,0x0,0x3000,0x0,0x20000000,0x0,0x0,0x0,0x0,0xc000,
      0x0,0x0,0x2001,0x1000000,0x0,0x0,0x20000,0x400000,0x0,0x40002000,0x0,0x1,0x2008,0x2000000,0x100,0x40240000,0x80200008,0x40000000,
      0x0,0x0,0x0,0x0,0x0,0x0,0x40,0x0,0x80040000,0x0,0x0,0x0,0x1000,0x48000000,0x1f,0x181f000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1040010,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x40000,0x60c,0x18,0x0,
      0x0,0x0,0x0,0x6000,0x0,0x10000000,0x0,0x0,0x0,0x0,0x4000,0x0,0x0,0x3800,0x7000000,0x0,0x0,0x840000,0x400000,0x0,0x40002000,
      0x0,0x2,0x2008,0x2000000,0x200,0x40440000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x40,0x0,0x80780000,0x0,0x0,0x0,0x1000,0x48000400,
      0x2,0x1e02000,0x0,0x0,0x80000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x80000,0x0,0x0,0x0,0x0,0x0,0x0,0x2040020,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x10,0x0,0x0,0x0,0x0,0x4000,0x0,0xf000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x780000,0x3800000,0x0,0x40002000,0x0,0xe,0x1808,0xc000000,0x3,0x80000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x80000000,
      0x0,0x0,0x0,0x1000,0x1c00,0x0,0x0,0x0,0x0,0x380000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x380000,0x0,0x0,0x0,0x0,0x0,0x0,0xe0400e0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3fc,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x8,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0 };

    // Definition of a 12x24 font.
     const unsigned int font12x24[12*24*256/32] = {
       0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
       0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x19,0x80000000,0x198000,0x0,0x0,0x0,0x0,
       0x0,0x198,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xc001806,0xc81980,0x60000000,0xc001806,0x1980c00,0x18060198,0xc80c,
       0x180600,0xc8198000,0xc001,0x80601980,0x18000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
       0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
       0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xf,0x0,0xf0000,0x0,0x0,0x0,0x0,0x0,0x198,0x0,0x0,0x0,0x0,0x0,0x0,
       0x0,0x0,0x0,0x0,0x0,0x0,0x600300f,0x1301980,0x90000000,0x600300f,0x1980600,0x300f0198,0x13006,0x300f01,0x30198000,0x6003,
       0xf01980,0x30000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
       0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
       0x0,0x0,0x0,0x0,0x0,0x0,0x6,0x0,0x60000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7007,0x3c0000,0x3006019,
       0x80000000,0x90000000,0x3006019,0x80000300,0x60198000,0x3,0x601980,0x0,0x3006,0x1980000,0x60000000,0x0,0x0,0xe0000000,0x0,
       0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
       0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x18000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x6000000,
       0x0,0x0,0x0,0x0,0x0,0xc800019,0x80000000,0x198000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xc0,0x0,0x0,0x1001,0x420000,0x0,0x0,0x90000000,
       0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x18000c06,0xc80001,0x10000000,0x18000c06,0x1800,0xc060000,0xc818,0xc0600,0xc8000000,
       0x18000,0xc0600000,0xc000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x6019,0x80660207,0x800f8060,0x300c004,0x0,0x6,
       0xe00703f,0x3f00383,0xf80f07fc,0x1f01f000,0x0,0xf8,0x607f,0x7c7e07,0xfe7fe0f8,0x6063fc1f,0x86066007,0xe7060f0,0x7f80f07f,
       0x81f8fff6,0x6606c03,0x70ee077f,0xe0786000,0xf0070000,0xc000060,0xc0,0x3e000,0x60006003,0x600fc00,0x0,0x0,0x0,0x0,0x0,0x3c0603,
       0xc0000000,0x7800000,0xf0000,0x0,0xf00001f,0x80001fe0,0x7fe000,0x0,0x0,0x0,0x168fe609,0x0,0x90e07,0x6000,0x3c000e,0x70000f8,
       0x1980001f,0x0,0x1f8,0xf00000f,0xf00180,0xfe000,0xe00e,0x1001,0x20060,0x6006006,0x600600,0x600fe07c,0x7fe7fe7f,0xe7fe3fc3,
       0xfc3fc3fc,0x7e07060f,0xf00f00,0xf00f0000,0xf360660,0x6606606e,0x76001e0,0xc00180f,0x1681981,0x10000000,0xc00180f,0x1980c00,
       0x180f0198,0x3801680c,0x180f01,0x68198000,0xc001,0x80f01980,0x18600198,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x6019,
       0x8044020c,0xc01f8060,0x2004004,0x0,0xc,0x3f81f07f,0x87f80383,0xf81f87fc,0x3f83f800,0x0,0x1fc,0x780607f,0x81fe7f87,0xfe7fe1fc,
       0x6063fc1f,0x860c6007,0xe7061f8,0x7fc1f87f,0xc3fcfff6,0x6606c03,0x30c6067f,0xe0783000,0xf00d8000,0x6000060,0xc0,0x7e000,0x60006003,
       0x600fc00,0x0,0x0,0xc00,0x0,0x0,0x7c0603,0xe0000000,0xfc00000,0x1f0000,0x0,0x900003f,0xc0003fe0,0x7fe000,0x0,0x0,0x0,0x1302660f,
       0x0,0xf0606,0x6004,0x7e0006,0x60601f8,0x19800001,0x80000000,0x1f8,0x19800010,0x81080300,0x3f2000,0x2011,0x1001,0x1c0060,0x6006006,
       0x600600,0x601fe1fe,0x7fe7fe7f,0xe7fe3fc3,0xfc3fc3fc,0x7f87061f,0x81f81f81,0xf81f8000,0x3fa60660,0x66066066,0x66003f0,0x6003009,
       0x1301981,0x10000000,0x6003009,0x1980600,0x30090198,0x1f013006,0x300901,0x30198000,0x6003,0x901980,0x30600198,0x0,0x0,0x0,
       0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x6019,0x80cc0f8c,0xc0180060,0x6006044,0x40000000,0xc,0x3181b041,0xc41c0783,0x388018,
       0x71c71800,0x0,0x106,0x18c0f061,0xc38261c6,0x600384,0x60606001,0x86186007,0xe78630c,0x60e30c60,0xe7040606,0x630cc03,0x39c30c00,
       0xc0603000,0x3018c000,0x3000060,0xc0,0x60000,0x60000000,0x6000c00,0x0,0x0,0xc00,0x0,0x0,0x600600,0x60000000,0x18400000,0x180000,
       0x0,0x19800070,0x40003600,0xc000,0x0,0x0,0x0,0x25a06,0x0,0x6030c,0x4,0xe20007,0xe060180,0xf000,0x80000000,0xf0000,0x10800000,
       0x80080600,0x7f2000,0x2020,0x80001001,0x20000,0xf00f00f,0xf00f00,0x601b0382,0x60060060,0x6000600,0x60060060,0x61c78630,0xc30c30c3,
       0xc30c000,0x30e60660,0x66066063,0xc600738,0x3006019,0x80000000,0xe0000000,0x3006019,0x80000300,0x60198000,0x3e000003,0x601980,
       0x0,0x3006,0x1980000,0x60600000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x6019,0x80cc1fcc,0xc0180060,0x6006035,0x80000000,
       0x18,0x71c03000,0xc00c0583,0x300018,0x60c60c00,0x0,0x6,0x3060f060,0xc30060c6,0x600300,0x60606001,0x86306007,0x9e78670e,0x60670e60,
       0x66000606,0x630c606,0x19830c01,0xc0601800,0x30306000,0x60,0xc0,0x60000,0x60000000,0x6000c00,0x0,0x0,0xc00,0x0,0x0,0x600600,
       0x60000000,0x18000000,0x300000,0x0,0x78060,0x6600,0x1c000,0x300c,0x39819c0,0x0,0x25a00,0x0,0x30c,0x4,0xc00003,0xc060180,0x30c1f,
       0x80000000,0x30c000,0x10800001,0x80700000,0x7f2000,0x2020,0x80001001,0x20060,0xf00f00f,0xf00f00,0xf01b0300,0x60060060,0x6000600,
       0x60060060,0x60c78670,0xe70e70e7,0xe70e000,0x70c60660,0x66066063,0xc7f8618,0x0,0x0,0x0,0x0,0x0,0x0,0x7000000,0x0,0x0,0x0,
       0x0,0x600000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x6019,0x87ff3a4c,0xc0180060,0x400600e,0x600000,0x18,0x60c03000,
       0xc00c0d83,0x700018,0x60c60c00,0x20,0x400006,0x3060f060,0xc6006066,0x600600,0x60606001,0x86606006,0x966c6606,0x60660660,0x66000606,
       0x630c666,0xf019801,0x80601800,0x30603000,0x1f06f,0xf01ec0,0xf03fe1ec,0x6703e01f,0x61c0c06,0xdc6701f0,0x6f01ec0c,0xe1f87fc6,
       0xc60cc03,0x71c60c7f,0xc0600600,0x60000000,0x30000000,0x300000,0x40040,0x88060,0x6600,0x18000,0x300c,0x1981980,0x0,0x2421f,
       0x80003ce0,0x7fc198,0x601f,0xc02021,0x980600c0,0x40230,0x80000000,0x402000,0x19806003,0x80006,0xc7f2000,0x2020,0x80001001,
       0x420060,0xf00f00f,0xf00f00,0xf01b0600,0x60060060,0x6000600,0x60060060,0x6066c660,0x66066066,0x6606208,0x60e60660,0x66066061,
       0x987fc670,0x1f01f01f,0x1f01f01,0xf039c0f0,0xf00f00f,0xf03e03,0xe03e03e0,0x1f06701f,0x1f01f01,0xf01f0060,0x1e660c60,0xc60c60c6,
       0xc6f060c,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x6000,0x7ff3207,0x8c0c0000,0xc00300e,0x600000,0x30,0x60c03000,
       0xc01c0983,0xf0600030,0x31860c06,0x6001e0,0x78000e,0x23e1f861,0xc6006066,0x600600,0x60606001,0x86c06006,0x966c6606,0x60660660,
       0xe7000606,0x630c666,0xf01f803,0x600c00,0x30000000,0x3f87f,0x83f83fc3,0xf83fe3fc,0x7f83e01f,0x6380c07,0xfe7f83f8,0x7f83fc0d,
       0xf3fc7fc6,0xc71cc03,0x3183187f,0xc0600600,0x60000000,0xff806000,0x300000,0x40040,0x88070,0x6600,0x60030060,0x6001818,0x1883180,
       0x0,0x2423f,0xc0007ff0,0x607fc1f8,0x603f,0x80c01fc1,0xf80601e0,0x5f220,0x80420000,0x5f2000,0xf006006,0x80006,0xc7f2000,0x2020,
       0x82107c07,0xc03c0060,0x1f81f81f,0x81f81f80,0xf03b0600,0x60060060,0x6000600,0x60060060,0x6066c660,0x66066066,0x660671c,0x61660660,
       0x66066061,0xf860e6c0,0x3f83f83f,0x83f83f83,0xf87fe3f8,0x3f83f83f,0x83f83e03,0xe03e03e0,0x3f87f83f,0x83f83f83,0xf83f8060,
       0x3fc60c60,0xc60c60c3,0x187f8318,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x6000,0x883200,0x300c0000,0xc003035,0x80600000,
       0x30,0x66c03001,0xc0f81983,0xf86f0030,0x1f071c06,0x600787,0xfe1e001c,0x6261987f,0x86006067,0xfe7fc600,0x7fe06001,0x87c06006,
       0xf6646606,0x60e6067f,0xc3e00606,0x61986f6,0x600f007,0x600c00,0x30000000,0x21c71,0x830831c3,0x1c06031c,0x71c06003,0x6700c06,
       0x6671c318,0x71831c0f,0x16040c06,0xc318606,0x1b031803,0x80600600,0x60000000,0x30009000,0x300000,0x40040,0x7003e,0x67e0,0x90070090,
       0x9001818,0x8c3100,0x0,0x60,0x4000e730,0x900380f0,0x6034,0x80c018c7,0xfe060338,0xb0121,0x80c60000,0x909000,0x6008,0x1080006,
       0xc3f2000,0x2011,0x3180060,0x60060e0,0x19819819,0x81981981,0x9833c600,0x7fe7fe7f,0xe7fe0600,0x60060060,0x60664660,0x66066066,
       0x66063b8,0x62660660,0x66066060,0xf06066c0,0x21c21c21,0xc21c21c2,0x1c466308,0x31c31c31,0xc31c0600,0x60060060,0x31871c31,0x83183183,
       0x18318000,0x71860c60,0xc60c60c3,0x18718318,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x6000,0x1981a00,0xe03e0000,0xc003044,
       0x40600000,0x60,0x66c03001,0x80f03182,0x1c7f8030,0x3f83fc06,0x601e07,0xfe078038,0x6661987f,0x86006067,0xfe7fc61e,0x7fe06001,
       0x87e06006,0x66666606,0x7fc6067f,0x81f80606,0x61986f6,0x6006006,0x600600,0x30000000,0xc60,0xc60060c6,0xc06060c,0x60c06003,
       0x6e00c06,0x6660c60c,0x60c60c0e,0x6000c06,0xc318666,0x1f031803,0x600600,0x603c2000,0x30016800,0x1fe0000,0x1f81f8,0x1c1f,0x804067e1,
       0x68060168,0x16800810,0xc42300,0x0,0x60,0x20c331,0x68030060,0x6064,0x3fc1040,0xf006031c,0xa011e,0x818c7fe0,0x909000,0x7fe1f,
       0x80f00006,0xc0f2060,0xf80e,0x18c0780,0x780781c0,0x19819819,0x81981981,0x9833c600,0x7fe7fe7f,0xe7fe0600,0x60060060,0xfc666660,
       0x66066066,0x66061f0,0x66660660,0x66066060,0x606066e0,0xc00c00,0xc00c00c0,0xc066600,0x60c60c60,0xc60c0600,0x60060060,0x60c60c60,
       0xc60c60c6,0xc60c000,0x61c60c60,0xc60c60c3,0x1860c318,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x6000,0x1980f81,0x80373000,
       0xc003004,0x7fe0001,0xf0000060,0x60c03003,0x183180,0xc71c060,0x3181ec00,0x7000,0xe070,0x66619860,0xc6006066,0x60061e,0x60606001,
       0x87606006,0x66626606,0x7f860661,0xc01c0606,0x6198696,0xf00600e,0x600600,0x30000000,0x1fc60,0xc60060c7,0xfc06060c,0x60c06003,
       0x7c00c06,0x6660c60c,0x60c60c0c,0x7f00c06,0xc3b8666,0xe01b007,0x3c00600,0x3c7fe000,0xff03ec00,0x1fe0000,0x40040,0xe001,0xc0806603,
       0xec0e03ec,0x3ec00010,0x0,0x60000000,0x7f,0x10c3f3,0xec070060,0x6064,0x3fc1040,0x6000030c,0xa0100,0x3187fe1,0xf09f1000,0x7fe00,
       0x6,0xc012060,0x0,0xc63c03,0xc03c0380,0x19819819,0x81981981,0x98330600,0x60060060,0x6000600,0x60060060,0xfc662660,0x66066066,
       0x66060e0,0x6c660660,0x66066060,0x6060e630,0x1fc1fc1f,0xc1fc1fc1,0xfc3fe600,0x7fc7fc7f,0xc7fc0600,0x60060060,0x60c60c60,0xc60c60c6,
       0xc60c7fe,0x62c60c60,0xc60c60c1,0xb060c1b0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x6000,0xffe02c6,0x3c633000,0xc003004,
       0x7fe0001,0xf00000c0,0x60c03006,0xc6180,0xc60c060,0x60c00c00,0x7000,0xe060,0x66639c60,0x66006066,0x600606,0x60606001,0x86306006,
       0x66636606,0x60060660,0xc0060606,0x61f8696,0xf00600c,0x600300,0x30000000,0x3fc60,0xc60060c7,0xfc06060c,0x60c06003,0x7c00c06,
       0x6660c60c,0x60c60c0c,0x1f80c06,0xc1b0666,0xe01b00e,0x3c00600,0x3c43c000,0x3007de00,0x600000,0x40040,0x30000,0x61006607,0xde0c07de,
       0x7de00000,0x0,0xf07fefff,0x1f,0x8008c3f7,0xde0e0060,0x6064,0xc01047,0xfe00018c,0xb013f,0x86300061,0xf0911000,0x6000,0x6,
       0xc012060,0x3f,0x8063c0cc,0x3cc0c700,0x39c39c39,0xc39c39c1,0x98630600,0x60060060,0x6000600,0x60060060,0x60663660,0x66066066,
       0x66061f0,0x78660660,0x66066060,0x607fc618,0x3fc3fc3f,0xc3fc3fc3,0xfc7fe600,0x7fc7fc7f,0xc7fc0600,0x60060060,0x60c60c60,0xc60c60c6,
       0xc60c7fe,0x64c60c60,0xc60c60c1,0xb060c1b0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x6000,0xffe0260,0x6661b000,0xc003000,
       0x600000,0xc0,0x60c0300c,0xc7fe0,0xc60c060,0x60c01c00,0x1e07,0xfe078060,0x6663fc60,0x66006066,0x600606,0x60606001,0x86386006,
       0x6636606,0x60060660,0xe0060606,0x60f039c,0x1b806018,0x600300,0x30000000,0x70c60,0xc60060c6,0x6060c,0x60c06003,0x7600c06,
       0x6660c60c,0x60c60c0c,0x1c0c06,0xc1b03fc,0xe01f01c,0xe00600,0x70000000,0x3007fc00,0x600000,0x40040,0x0,0x62006607,0xfc1807fc,
       0x7fc00000,0x0,0xf0000000,0x1,0xc004c307,0xfc1c0060,0x6064,0xc018c0,0x600000d8,0x5f200,0x3180060,0x50a000,0x6000,0x6,0xc012000,
       0x0,0xc601c0,0x4201c600,0x3fc3fc3f,0xc3fc3fc3,0xfc7f0600,0x60060060,0x6000600,0x60060060,0x60663660,0x66066066,0x66063b8,
       0x70660660,0x66066060,0x607f860c,0x70c70c70,0xc70c70c7,0xcc60600,0x60060060,0x6000600,0x60060060,0x60c60c60,0xc60c60c6,0xc60c000,
       0x68c60c60,0xc60c60c1,0xf060c1f0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3300260,0x6661e000,0xc003000,0x600000,
       0x180,0x71c03018,0xc7fe0,0xc60c0c0,0x60c01800,0x787,0xfe1e0060,0x6663fc60,0x630060c6,0x600306,0x60606001,0x86186006,0x661e70e,
       0x60070c60,0x60060606,0x60f039c,0x19806038,0x600180,0x30000000,0x60c60,0xc60060c6,0x6060c,0x60c06003,0x6700c06,0x6660c60c,
       0x60c60c0c,0xc0c06,0xc1b039c,0x1f00e018,0x600600,0x60000000,0x1803f800,0x600000,0x40040,0x39e00,0x63006603,0xf83803f8,0x3f800000,
       0x0,0x60000000,0x0,0xc00cc303,0xf8180060,0x6064,0xc01fc0,0x60060070,0x40200,0x18c0060,0x402000,0x6000,0x6,0xc012000,0x0,0x18c0140,
       0x2014600,0x3fc3fc3f,0xc3fc3fc3,0xfc7f0300,0x60060060,0x6000600,0x60060060,0x60c61e70,0xe70e70e7,0xe70e71c,0x60e60660,0x66066060,
       0x6060060c,0x60c60c60,0xc60c60c6,0xcc60600,0x60060060,0x6000600,0x60060060,0x60c60c60,0xc60c60c6,0xc60c000,0x70c60c60,0xc60c60c0,
       0xe060c0e0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x33022e0,0x6670c000,0xc003000,0x600600,0x60180,0x31803030,
       0x41c0184,0x1831c0c0,0x71c23806,0x6001e0,0x780000,0x62630c60,0xe38261c6,0x600386,0x60606043,0x860c6006,0x661e30c,0x60030c60,
       0x740e0607,0xe0f039c,0x31c06030,0x600180,0x30000000,0x61c71,0x830831c3,0x406031c,0x60c06003,0x6300c06,0x6660c318,0x71831c0c,
       0x41c0c07,0x1c0e039c,0x1b00e030,0x600600,0x60000000,0x1c41b00e,0x601cc0,0x401f8,0x45240,0xe1803601,0xb03001b0,0x1b000000,
       0x0,0x0,0x41,0xc008e711,0xb0300060,0x6034,0x80c02020,0x60060030,0x30c00,0xc60000,0x30c000,0x0,0x7,0x1c012000,0x0,0x3180240,
       0x6024608,0x30c30c30,0xc30c30c3,0xc630382,0x60060060,0x6000600,0x60060060,0x61c61e30,0xc30c30c3,0xc30c208,0x70c70e70,0xe70e70e0,
       0x6060068c,0x61c61c61,0xc61c61c6,0x1cc62308,0x30430430,0x43040600,0x60060060,0x31860c31,0x83183183,0x18318060,0x31c71c71,
       0xc71c71c0,0xe07180e0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x6000,0x2203fc0,0x663f6000,0x6006000,0x600600,0x60300,
       0x3f81fe7f,0xc7f80187,0xf83f80c0,0x3f83f006,0x600020,0x400060,0x33e6067f,0xc1fe7f87,0xfe6001fe,0x6063fc7f,0x60e7fe6,0x660e3f8,
       0x6001f860,0x37fc0603,0xfc06030c,0x30c0607f,0xe06000c0,0x30000000,0x7fc7f,0x83f83fc3,0xfc0603fc,0x60c7fe03,0x61807c6,0x6660c3f8,
       0x7f83fc0c,0x7f80fc3,0xfc0e039c,0x3180607f,0xc0600600,0x60000000,0xfc0e00c,0x601986,0x66040040,0x4527f,0xc0803fe0,0xe07fe0e0,
       0xe000000,0x0,0x0,0x7f,0x80107ff0,0xe07fc060,0x603f,0x83fe0000,0x60060018,0xf000,0x420000,0xf0000,0x7fe00,0x7,0xfe012000,
       0x0,0x2100640,0xc0643f8,0x60660660,0x66066067,0xec3e1fe,0x7fe7fe7f,0xe7fe3fc3,0xfc3fc3fc,0x7f860e3f,0x83f83f83,0xf83f8000,
       0x5fc3fc3f,0xc3fc3fc0,0x606006fc,0x7fc7fc7f,0xc7fc7fc7,0xfcffe3f8,0x3fc3fc3f,0xc3fc7fe7,0xfe7fe7fe,0x3f860c3f,0x83f83f83,
       0xf83f8060,0x7f83fc3f,0xc3fc3fc0,0x607f8060,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x6000,0x2201f80,0x3c1e7000,0x6006000,
       0x600,0x60300,0xe01fe7f,0xc3f00183,0xe01f0180,0x1f01e006,0x600000,0x60,0x3006067f,0x807c7e07,0xfe6000f8,0x6063fc3e,0x6067fe6,
       0x660e0f0,0x6000f060,0x3bf80601,0xf806030c,0x60e0607f,0xe06000c0,0x30000000,0x1ec6f,0xf01ec0,0xf80601ec,0x60c7fe03,0x61c03c6,
       0x6660c1f0,0x6f01ec0c,0x3f007c1,0xcc0e030c,0x71c0c07f,0xc0600600,0x60000000,0x7804018,0xe01186,0x66040040,0x39e3f,0x80401fe0,
       0x407fe040,0x4000000,0x0,0x0,0x3f,0x203ce0,0x407fc060,0x601f,0x3fe0000,0x60060018,0x0,0x0,0x0,0x7fe00,0x6,0xe6012000,0x0,
       0x7e0,0x1807e1f0,0x60660660,0x66066066,0x6c3e07c,0x7fe7fe7f,0xe7fe3fc3,0xfc3fc3fc,0x7e060e0f,0xf00f00,0xf00f0000,0x8f01f81f,
       0x81f81f80,0x60600670,0x1ec1ec1e,0xc1ec1ec1,0xec79c0f0,0xf80f80f,0x80f87fe7,0xfe7fe7fe,0x1f060c1f,0x1f01f01,0xf01f0000,0x4f01cc1c,
       0xc1cc1cc0,0xc06f00c0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x200,0x0,0x6006000,0x600,0x600,0x0,0x0,0x0,0x0,
       0x600000,0x0,0x18000000,0x0,0x0,0x0,0x0,0x0,0x1800,0x0,0x0,0x0,0x600060,0x30000000,0x0,0x0,0xc,0x3,0x0,0x0,0x60000c00,0x0,
       0x0,0xc000,0x600600,0x60000000,0x18,0xc03100,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x4,0x0,0x601f8,0x0,0x0,0x0,0x0,0x6,
       0x12000,0x2000000,0x40,0x20004000,0x0,0x0,0x10,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x10,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
       0x0,0xc06000c0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x200,0x0,0x2004000,0xc00,0x0,0x0,0x0,0x0,0x0,0xc00000,
       0x0,0x1c000000,0x0,0x0,0x0,0x0,0x0,0xc00,0x0,0x0,0x0,0x780000,0xf0000000,0x0,0x0,0x21c,0x3,0x0,0x0,0x60000c00,0x0,0x0,0xc000,
       0x7c0603,0xe0000000,0x10,0xc02300,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x4,0x0,0x601f0,0x0,0x0,0x0,0x0,0x6,0x12000,0x1000000,
       0x40,0x7e004000,0x0,0x0,0x8,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x8,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xc06000c0,0x0,
       0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x200,0x0,0x300c000,0xc00,0x0,0x0,0x0,0x0,0x0,0xc00000,0x0,0x7800000,0x0,
       0x0,0x0,0x0,0x0,0x800,0x0,0x0,0x0,0x780000,0xf0000000,0x0,0x0,0x3f8,0x3e,0x0,0x0,0x60000c00,0x0,0x0,0x38000,0x3c0603,0xc0000000,
       0x10,0xfc00000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x4,0x0,0x60000,0x0,0x0,0x0,0x0,0x6,0x0,0x1000000,0x0,0x0,0x0,0x0,
       0x8,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x8,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3,0x80600380,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
       0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xffc,0x0,
       0x0,0x1f0,0x3c,0x0,0x0,0x60000c00,0x0,0x0,0x38000,0x600,0x0,0x0,0xf000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
       0x0,0x0,0x0,0x0,0x0,0x6,0x0,0xe000000,0x0,0x0,0x0,0x0,0x70,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x70,0x0,0x0,0x0,0x0,
       0x0,0x0,0x0,0x3,0x80600380,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
       0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xffc,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x600,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
       0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
       0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0 };

    // Definition of a 16x32 font.
    const unsigned int font16x32[16*32*256/32] = {
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xc300000,0x0,0xc300000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xe70,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x70000e0,0x3c00730,0xe7001c0,0x0,0x70000e0,0x3c00e70,0x70000e0,0x3c00e70,0x730,0x70000e0,0x3c00730,
      0xe700000,0x700,0xe003c0,0xe7000e0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x6600000,0x0,0x6600000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xe70,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x18001c0,0x6600ff0,0xe7003e0,0x0,0x18001c0,0x6600e70,0x18001c0,0x6600e70,0xff0,0x18001c0,0x6600ff0,0xe700000,0x180,
      0x1c00660,0xe7001c0,0x0,0x0,0x0,0x380,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c00000,
      0x0,0x3c00000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xe70,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c00380,
      0xc300ce0,0xe700630,0x0,0x1c00380,0xc300e70,0x1c00380,0xc300e70,0xce0,0x1c00380,0xc300ce0,0xe700000,0x1c0,0x3800c30,0xe700380,
      0x0,0x0,0x0,0x7c0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0xe000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1800000,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0xc300000,0x0,0xc300000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x700000,0x0,0x0,0x0,0x7c007c00,0x3e000000,
      0x0,0x0,0x630,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xe000070,0x1800000,0xc60,0x0,0xe000070,0x1800000,0xe000070,
      0x1800000,0x0,0xe000070,0x1800000,0x0,0xe00,0x700180,0x70,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x800000,0x0,0x600600,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x3f0,0xfc0,0x0,0x7000000,0x38000000,0x1c0000,0xfc0000,0x380001c0,0xe01c00,0x7f800000,0x0,0x0,0x0,0x0,0x0,0x0,0x7c,
      0x1801f00,0x0,0x0,0x1c,0x0,0x0,0x3c00000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7300000,0x6600000,0x0,0x6600000,0x0,0x0,0x0,0x0,0xe700000,
      0x0,0x0,0x0,0x0,0x0,0xe00000,0x0,0x0,0x0,0xc000c00,0x43800000,0x0,0x0,0x630,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0xf80,0x70000e0,0x3c00730,0xe700c60,0x0,0x70000e0,0x3c00e70,0x70000e0,0x3c00e70,0xe000730,0x70000e0,0x3c00730,0xe700000,0x700,
      0xe003c0,0xe7000e0,0x38000e70,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c0,0x6300000,0x803c00,0x7c00180,
      0xc00300,0x1000000,0x0,0x1c,0x3c007c0,0xfc007e0,0xe01ff8,0x3f03ffc,0x7e007c0,0x0,0x0,0x7c0,0x1c0,0x7f8003f0,0x7f007ff8,0x7ff803f0,
      0x70381ffc,0xff0700e,0x7000783c,0x783807c0,0x7fc007c0,0x7fc00fc0,0x7fff7038,0x700ee007,0x780f780f,0x7ffc03f0,0x70000fc0,0x3c00000,
      0x3000000,0x38000000,0x1c0000,0x1fc0000,0x380001c0,0xe01c00,0x7f800000,0x0,0x0,0x0,0x0,0x0,0x0,0xfc,0x1801f80,0x0,0x1f80000,
      0x7e,0x0,0x0,0x2400000,0xfc00000,0x7ff0000,0x7ffc0000,0x0,0x0,0x0,0x0,0xf30fb0c,0x2400000,0x0,0x240780f,0x1c0,0xfc,0x780f,
      0x18003f0,0xe700000,0x7c00000,0x0,0xff0,0x3c00000,0x78007c0,0xc00000,0xff80000,0xf80,0x7c00000,0xc000c00,0x18001c0,0x1c001c0,
      0x1c001c0,0x1c003e0,0x7fe03f0,0x7ff87ff8,0x7ff87ff8,0x1ffc1ffc,0x1ffc1ffc,0x7f007838,0x7c007c0,0x7c007c0,0x7c00000,0x7c67038,
      0x70387038,0x7038780f,0x70001fe0,0x30000c0,0x2400f30,0xe700c60,0x0,0x30000c0,0x2400e70,0x30000c0,0x2400e70,0xf700f30,0x30000c0,
      0x2400f30,0xe700000,0x300,0xc00240,0xe7000c0,0x38000e70,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c0,
      0x630018c,0x807e00,0xfe00180,0xc00300,0x1000000,0x0,0x38,0xff01fc0,0x3ff01ff0,0x1e01ff8,0x7f83ffc,0x1ff80ff0,0x0,0x0,0xff0,
      0x1f003e0,0x7fe00ff8,0x7fc07ff8,0x7ff80ff8,0x70381ffc,0xff0701c,0x7000783c,0x78381ff0,0x7fe01ff0,0x7fe01ff0,0x7fff7038,0x781ee007,
      0x3c1e380e,0x7ffc0380,0x380001c0,0x3c00000,0x1800000,0x38000000,0x1c0000,0x3c00000,0x380001c0,0xe01c00,0x3800000,0x0,0x0,
      0x0,0x7000000,0x0,0x0,0x1e0,0x18003c0,0x0,0x3fc0000,0x70,0x0,0x0,0x6600000,0x1ff00000,0x1fff0000,0x7ffc0000,0x0,0x0,0x0,0x0,
      0xcf0239c,0x3c00000,0x0,0x3c0380e,0x1c0,0x2001fe,0x380e,0x18007f8,0xe700000,0x8600000,0x0,0xff0,0x7e00000,0x8c00870,0x1800000,
      0x1ff80000,0x180,0xc600000,0xc000c00,0x38001c0,0x3e003e0,0x3e003e0,0x3e001c0,0x7fe0ff8,0x7ff87ff8,0x7ff87ff8,0x1ffc1ffc,0x1ffc1ffc,
      0x7fc07838,0x1ff01ff0,0x1ff01ff0,0x1ff00000,0x1fec7038,0x70387038,0x7038380e,0x70003ce0,0x1800180,0x6600cf0,0xe7007c0,0x0,
      0x1800180,0x6600e70,0x1800180,0x6600e70,0x7c00cf0,0x1800180,0x6600cf0,0xe700000,0x180,0x1800660,0xe700180,0x38000e70,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c0,0x630030c,0x3f0e700,0x1e200180,0x1800180,0x21100000,0x0,
      0x38,0x1e7819c0,0x38781038,0x1e01c00,0xf080038,0x1c381c38,0x0,0x0,0x1878,0x7fc03e0,0x70e01e18,0x70e07000,0x70001e18,0x703801c0,
      0x707038,0x70007c7c,0x7c381c70,0x70701c70,0x70703830,0x1c07038,0x381ce007,0x1c1c3c1e,0x3c0380,0x380001c0,0x7e00000,0xc00000,
      0x38000000,0x1c0000,0x3800000,0x38000000,0x1c00,0x3800000,0x0,0x0,0x0,0x7000000,0x0,0x0,0x1c0,0x18001c0,0x0,0x70c0000,0xe0,
      0x0,0x0,0xc300000,0x38300000,0x3c700000,0x3c0000,0x0,0x0,0x0,0x0,0xce022f4,0x1800000,0x0,0x1803c1e,0x1c0,0x2003c2,0x3c1e,
      0x1800e08,0x7e0,0x300000,0x0,0x7e00000,0xe700000,0x600030,0x3000000,0x3f980000,0x180,0x18200000,0xc000c00,0x1e0001c0,0x3e003e0,
      0x3e003e0,0x3e003e0,0xfe01e18,0x70007000,0x70007000,0x1c001c0,0x1c001c0,0x70e07c38,0x1c701c70,0x1c701c70,0x1c700000,0x3c787038,
      0x70387038,0x70383c1e,0x70003870,0xc00300,0xc300ce0,0x380,0x0,0xc00300,0xc300000,0xc00300,0xc300000,0xfc00ce0,0xc00300,0xc300ce0,
      0x0,0xc0,0x3000c30,0x300,0x38000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c0,0x630031c,0xff8c300,
      0x1c000180,0x1800180,0x39380000,0x0,0x70,0x1c3801c0,0x203c001c,0x3e01c00,0x1c000038,0x381c3838,0x0,0x0,0x1038,0xe0e03e0,0x70703c08,
      0x70707000,0x70003808,0x703801c0,0x707070,0x70007c7c,0x7c383838,0x70383838,0x70387010,0x1c07038,0x381c700e,0x1e3c1c1c,0x780380,
      0x1c0001c0,0xe700000,0x0,0x38000000,0x1c0000,0x3800000,0x38000000,0x1c00,0x3800000,0x0,0x0,0x0,0x7000000,0x0,0x0,0x1c0,0x18001c0,
      0x0,0xe000000,0xe0,0x0,0x1000100,0x3800,0x70100000,0x38700000,0x780000,0x1c0,0x7801ce0,0xe380000,0x0,0x2264,0x0,0x0,0x1c1c,
      0x0,0x200780,0x1c1c,0x1800c00,0x1818,0x7f00000,0x0,0x18180000,0xc300000,0x600070,0x0,0x7f980000,0x180,0x18300000,0xc000c00,
      0x3000000,0x3e003e0,0x3e003e0,0x3e003e0,0xee03c08,0x70007000,0x70007000,0x1c001c0,0x1c001c0,0x70707c38,0x38383838,0x38383838,
      0x38380000,0x38387038,0x70387038,0x70381c1c,0x7fc03870,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xbc00000,0x0,0x0,0x0,0x0,0x0,0x0,
      0x38000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c0,0x6300318,0xe88c300,0x1c000180,0x38001c0,
      0xfe00180,0x0,0x70,0x1c3801c0,0x1c001c,0x6e01c00,0x1c000078,0x381c3818,0x0,0x40000,0x40000038,0x1c0607e0,0x70703800,0x70707000,
      0x70003800,0x703801c0,0x7070e0,0x70007c7c,0x7c383838,0x70383838,0x70387000,0x1c07038,0x381c700e,0xf780e38,0x700380,0x1c0001c0,
      0x1c380000,0x0,0x38000000,0x1c0000,0x3800000,0x38000000,0x1c00,0x3800000,0x0,0x0,0x0,0x7000000,0x0,0x0,0x1c0,0x18001c0,0x0,
      0xe000000,0xe0,0x0,0x1000100,0x4400,0x70000000,0x38700000,0x700000,0xe0,0x7001c70,0xe380000,0x0,0x2264,0x0,0x0,0xe38,0x0,
      0x200700,0xe38,0x1800c00,0x300c,0xc300000,0x0,0x300c0000,0xc300180,0x6003c0,0x0,0x7f980000,0x180,0x18300000,0xc000c00,0x1800000,
      0x7e007e0,0x7e007e0,0x7e003e0,0xee03800,0x70007000,0x70007000,0x1c001c0,0x1c001c0,0x70707c38,0x38383838,0x38383838,0x38380000,
      0x38387038,0x70387038,0x70380e38,0x7ff039f0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1e00000,0x0,0x0,0x0,0x40000,0x0,0x0,0x38000000,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c0,0x6300318,0x1c80e700,0x1c000180,0x38001c0,0x3800180,
      0x0,0xe0,0x381c01c0,0x1c001c,0x6e01c00,0x38000070,0x381c381c,0x0,0x3c0000,0x78000078,0x38030770,0x70707800,0x70387000,0x70007000,
      0x703801c0,0x7071c0,0x7000745c,0x7638701c,0x7038701c,0x70387000,0x1c07038,0x1c38718e,0x7700f78,0xf00380,0xe0001c0,0x381c0000,
      0x7e0,0x39e003e0,0x79c03f0,0x3ffc079c,0x39e01fc0,0xfe01c1e,0x3807778,0x39e007e0,0x39e0079c,0x73c07e0,0x7ff83838,0x701ce007,
      0x783c701c,0x1ffc01c0,0x18001c0,0x0,0x1c000100,0xe0,0x0,0x1000100,0x4200,0x70000000,0x70700100,0xf00100,0x10000e0,0x7000c70,
      0xc700000,0x0,0x2204,0x7e00000,0x1e380100,0x1ffc0f78,0x0,0xf80700,0xf78,0x1800e00,0x63e6,0x18300000,0x0,0x6fe60000,0xe700180,
      0xc00060,0x3838,0x7f980000,0x180,0x18300000,0xc000c00,0x18001c0,0x7700770,0x7700770,0x77007f0,0xee07800,0x70007000,0x70007000,
      0x1c001c0,0x1c001c0,0x70387638,0x701c701c,0x701c701c,0x701c1008,0x707c7038,0x70387038,0x70380f78,0x707039c0,0x7e007e0,0x7e007e0,
      0x7e007e0,0x1f3c03e0,0x3f003f0,0x3f003f0,0x1fc01fc0,0x1fc01fc0,0x7f039e0,0x7e007e0,0x7e007e0,0x7e00380,0x7ce3838,0x38383838,
      0x3838701c,0x39e0701c,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c0,0x6307fff,0x1c807e0c,0xe000180,
      0x30000c0,0x3800180,0x0,0xe0,0x381c01c0,0x1c001c,0xce01fe0,0x38000070,0x381c381c,0x3800380,0xfc0000,0x7e0000f0,0x30030770,
      0x70707000,0x70387000,0x70007000,0x703801c0,0x707380,0x700076dc,0x7638701c,0x7038701c,0x70387800,0x1c07038,0x1c3873ce,0x7f00770,
      0xe00380,0xe0001c0,0x700e0000,0x1ff8,0x3ff00ff0,0xffc0ff8,0x3ffc0ffc,0x3bf01fc0,0xfe01c3c,0x3807f78,0x3bf00ff0,0x3ff00ffc,
      0x77e0ff0,0x7ff83838,0x3838e007,0x3c783838,0x1ffc01c0,0x18001c0,0x0,0x7ff00380,0x1e0,0x0,0x1000100,0x4200,0x78000000,0x70700380,
      0xe00380,0x3800060,0xe000e30,0x1c600000,0x0,0x2204,0xff00000,0x7f7c0380,0x1ffc0770,0x1c0,0x3fc0700,0x18040770,0x1800780,0x4e12,
      0x18300104,0x0,0x4c320000,0x7e00180,0x1c00030,0x3838,0x7f980000,0x180,0x18302080,0xc000c00,0x18001c0,0x7700770,0x7700770,
      0x7700770,0x1ee07000,0x70007000,0x70007000,0x1c001c0,0x1c001c0,0x70387638,0x701c701c,0x701c701c,0x701c381c,0x705c7038,0x70387038,
      0x70380770,0x70383b80,0x1ff81ff8,0x1ff81ff8,0x1ff81ff8,0x3fbe0ff0,0xff80ff8,0xff80ff8,0x1fc01fc0,0x1fc01fc0,0xff83bf0,0xff00ff0,
      0xff00ff0,0xff00380,0xffc3838,0x38383838,0x38383838,0x3ff03838,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x1c0,0x7fff,0x1c803c38,0xf000000,0x70000e0,0xfe00180,0x0,0x1c0,0x381c01c0,0x3c0078,0xce01ff0,0x39e000f0,0x1c38381c,0x3800380,
      0x3e07ffc,0xf8001f0,0x307b0770,0x70e07000,0x70387000,0x70007000,0x703801c0,0x707700,0x700076dc,0x7638701c,0x7038701c,0x70387e00,
      0x1c07038,0x1c3873ce,0x3e007f0,0x1e00380,0x70001c0,0x0,0x1038,0x3c381e18,0x1c7c1e3c,0x3801e3c,0x3c7801c0,0xe01c78,0x380739c,
      0x3c781c38,0x3c381c3c,0x7c21e10,0x7003838,0x3838700e,0x1ef03838,0x3c01c0,0x18001c0,0x0,0x7fe007c0,0x1c0,0x0,0x1000100,0x6400,
      0x7e000000,0x707007c0,0x1e007c0,0x7c00070,0xe000638,0x18600000,0x0,0x0,0x1e100000,0x73ce07c0,0x3c07f0,0x1c0,0x7240700,0x1ddc3ffe,
      0x1800de0,0x8c01,0x1870030c,0x0,0x8c310000,0x3c00180,0x3800030,0x3838,0x7f980000,0x180,0x183030c0,0xc000c00,0x430001c0,0x7700770,
      0x7700770,0x7700770,0x1ce07000,0x70007000,0x70007000,0x1c001c0,0x1c001c0,0x70387638,0x701c701c,0x701c701c,0x701c1c38,0x70dc7038,
      0x70387038,0x703807f0,0x70383b80,0x10381038,0x10381038,0x10381038,0x21e71e18,0x1e3c1e3c,0x1e3c1e3c,0x1c001c0,0x1c001c0,0x1e383c78,
      0x1c381c38,0x1c381c38,0x1c380380,0x1c383838,0x38383838,0x38383838,0x3c383838,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x1c0,0x630,0x1e8000e0,0x1f000000,0x70000e0,0x39380180,0x0,0x1c0,0x3b9c01c0,0x3c07f0,0x18e01078,0x3bf800e0,
      0x7e0383c,0x3800380,0x1f807ffc,0x3f001c0,0x61ff0e38,0x7fc07000,0x70387ff0,0x7ff07000,0x7ff801c0,0x707f00,0x7000729c,0x7338701c,
      0x7070701c,0x70703fc0,0x1c07038,0x1e7873ce,0x1c003e0,0x3c00380,0x70001c0,0x0,0x1c,0x3c381c00,0x1c3c1c1c,0x3801c3c,0x383801c0,
      0xe01cf0,0x380739c,0x38381c38,0x3c381c3c,0x7801c00,0x7003838,0x3838700e,0xfe03c78,0x7801c0,0x18001c0,0x0,0x1c000c20,0xff8,
      0x0,0x1ff01ff0,0x3818,0x3fc00100,0x707e0c20,0x3c00c20,0xc200030,0xc000618,0x18c00000,0x0,0x0,0x1c000080,0xe1ce0c20,0x7803e0,
      0x1c0,0xe200700,0xff83ffe,0x1801878,0x9801,0x1cf0071c,0x7ffc0000,0x8c310000,0x7ffe,0x7000030,0x3838,0x3f980380,0x180,0xc6038e0,
      0x7f9c7f9c,0x3e1c01c0,0xe380e38,0xe380e38,0xe380f78,0x1cfc7000,0x7ff07ff0,0x7ff07ff0,0x1c001c0,0x1c001c0,0xfe387338,0x701c701c,
      0x701c701c,0x701c0e70,0x719c7038,0x70387038,0x703803e0,0x70383b80,0x1c001c,0x1c001c,0x1c001c,0xe71c00,0x1c1c1c1c,0x1c1c1c1c,
      0x1c001c0,0x1c001c0,0x1c383838,0x1c381c38,0x1c381c38,0x1c380000,0x3c383838,0x38383838,0x38383c78,0x3c383c78,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c0,0x630,0xf800380,0x3f830000,0x70000e0,0x31080180,0x0,0x380,0x3b9c01c0,
      0x7807e0,0x38e00038,0x3c3800e0,0xff01c3c,0x3800380,0x7c000000,0x7c03c0,0x61870e38,0x7fc07000,0x70387ff0,0x7ff070fc,0x7ff801c0,
      0x707f80,0x7000739c,0x7338701c,0x7ff0701c,0x7fe00ff0,0x1c07038,0xe7073ce,0x1c003e0,0x3800380,0x38001c0,0x0,0x1c,0x381c3800,
      0x381c380e,0x380381c,0x383801c0,0xe01de0,0x380739c,0x3838381c,0x381c381c,0x7001e00,0x7003838,0x1c70718e,0x7e01c70,0xf00380,
      0x18001e0,0x1e000000,0x1c001bb0,0xff8,0x0,0x1000100,0xe0,0xff00300,0x707e1bb0,0x3801bb0,0x1bb00010,0x8000308,0x30c00000,0x0,
      0x0,0x1e0000c0,0xe1ce1bb0,0xf003e0,0x1c0,0x1c203ff8,0x63003e0,0x180181c,0x9801,0xfb00e38,0x7ffc0000,0x8fc10000,0x7ffe,0xe000860,
      0x3838,0x1f980380,0x180,0x7c01c70,0x1f001f0,0x1f003c0,0xe380e38,0xe380e38,0xe380e38,0x1cfc7000,0x7ff07ff0,0x7ff07ff0,0x1c001c0,
      0x1c001c0,0xfe387338,0x701c701c,0x701c701c,0x701c07e0,0x731c7038,0x70387038,0x703803e0,0x70383980,0x1c001c,0x1c001c,0x1c001c,
      0xe73800,0x380e380e,0x380e380e,0x1c001c0,0x1c001c0,0x381c3838,0x381c381c,0x381c381c,0x381c0000,0x387c3838,0x38383838,0x38381c70,
      0x381c1c70,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c0,0xc30,0x7f00e00,0x33c30000,0x70000e0,0x1007ffe,
      0x0,0x380,0x3b9c01c0,0xf00078,0x30e0001c,0x3c1c01c0,0x1c381fdc,0x0,0x70000000,0x1c0380,0x63030e38,0x70707000,0x70387000,0x700070fc,
      0x703801c0,0x707b80,0x7000739c,0x7338701c,0x7fc0701c,0x7fc001f0,0x1c07038,0xe703e5c,0x3e001c0,0x7800380,0x38001c0,0x0,0x7fc,
      0x381c3800,0x381c380e,0x380381c,0x383801c0,0xe01fe0,0x380739c,0x3838381c,0x381c381c,0x7001fc0,0x7003838,0x1c70718e,0x7c01c70,
      0xe01f00,0x180007c,0x7f8c0000,0x7fc03fb8,0x1c0,0x0,0x1000100,0x700,0x1f00600,0x70703fb8,0x7803fb8,0x3fb80000,0x8000000,0x180,
      0x0,0x0,0x1fc00060,0xe1ce3fb8,0xe001c0,0x1c0,0x1c203ff8,0xc1801c0,0x180c,0x9801,0x1c70,0xc0000,0x8cc10000,0x180,0xfe007c0,
      0x3838,0x7980380,0xff0,0xe38,0x3e003e00,0x3e000380,0xe380e38,0xe380e38,0xe380e38,0x38e07000,0x70007000,0x70007000,0x1c001c0,
      0x1c001c0,0x70387338,0x701c701c,0x701c701c,0x701c03c0,0x731c7038,0x70387038,0x703801c0,0x703838e0,0x7fc07fc,0x7fc07fc,0x7fc07fc,
      0xe73800,0x380e380e,0x380e380e,0x1c001c0,0x1c001c0,0x381c3838,0x381c381c,0x381c381c,0x381c7ffc,0x38dc3838,0x38383838,0x38381c70,
      0x381c1c70,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c0,0xc60,0xf83878,0x71e30000,0x70000e0,0x1007ffe,
      0x7f0,0x380,0x381c01c0,0x1e0003c,0x60e0001c,0x381c01c0,0x381c079c,0x0,0x7c000000,0x7c0380,0x63031c1c,0x70307000,0x70387000,
      0x7000701c,0x703801c0,0x7071c0,0x7000739c,0x71b8701c,0x7000701c,0x71e00078,0x1c07038,0xe703e7c,0x7e001c0,0xf000380,0x38001c0,
      0x0,0x1ffc,0x381c3800,0x381c3ffe,0x380381c,0x383801c0,0xe01fc0,0x380739c,0x3838381c,0x381c381c,0x7000ff0,0x7003838,0x1ef03bdc,
      0x3800ee0,0x1e01f00,0x180007c,0x61fc0000,0x7fc07f3c,0x1c0,0x0,0x1000100,0x1800,0x780c00,0x70707f3c,0xf007f3c,0x7f3c0000,0x0,
      0x3c0,0x3ffcffff,0x0,0xff00030,0xe1fe7f3c,0x1e001c0,0x1c0,0x1c200700,0xc183ffe,0xe0c,0x9801,0x1ff038e0,0xc07f0,0x8c610000,
      0x180,0x0,0x3838,0x1980380,0x0,0x1ff0071c,0xe000e000,0xe0000f80,0x1c1c1c1c,0x1c1c1c1c,0x1c1c1e38,0x38e07000,0x70007000,0x70007000,
      0x1c001c0,0x1c001c0,0x703871b8,0x701c701c,0x701c701c,0x701c03c0,0x761c7038,0x70387038,0x703801c0,0x70703870,0x1ffc1ffc,0x1ffc1ffc,
      0x1ffc1ffc,0xfff3800,0x3ffe3ffe,0x3ffe3ffe,0x1c001c0,0x1c001c0,0x381c3838,0x381c381c,0x381c381c,0x381c7ffc,0x389c3838,0x38383838,
      0x38380ee0,0x381c0ee0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c0,0xfffc,0xbc60fc,0x70e30000,0x70000e0,
      0x180,0x7f0,0x700,0x381c01c0,0x3e0001c,0x7ffc001c,0x381c03c0,0x381c001c,0x0,0x1f807ffc,0x3f00380,0x63031ffc,0x70387000,0x70387000,
      0x7000701c,0x703801c0,0x7071e0,0x7000701c,0x71b8701c,0x7000701c,0x70f00038,0x1c07038,0x7e03e7c,0x77001c0,0xe000380,0x1c001c0,
      0x0,0x3c1c,0x381c3800,0x381c3ffe,0x380381c,0x383801c0,0xe01fe0,0x380739c,0x3838381c,0x381c381c,0x70003f8,0x7003838,0xee03bdc,
      0x3c00ee0,0x3c00380,0x18000e0,0xf00000,0x1c007e7c,0x3c0,0x0,0x1000100,0x0,0x381800,0x70707e7c,0xe007e7c,0x7e7c0000,0x0,0x7c0,
      0x0,0x0,0x3f80018,0xe1fe7e7c,0x3c001c0,0x1c0,0x1c200700,0xc183ffe,0xf0c,0x8c01,0x38e0,0xc07f0,0x8c710000,0x180,0x0,0x3838,
      0x1980000,0x0,0x71c,0x7000f0,0x700f00,0x1ffc1ffc,0x1ffc1ffc,0x1ffc1ffc,0x3fe07000,0x70007000,0x70007000,0x1c001c0,0x1c001c0,
      0x703871b8,0x701c701c,0x701c701c,0x701c07e0,0x7c1c7038,0x70387038,0x703801c0,0x7ff03838,0x3c1c3c1c,0x3c1c3c1c,0x3c1c3c1c,
      0x3fff3800,0x3ffe3ffe,0x3ffe3ffe,0x1c001c0,0x1c001c0,0x381c3838,0x381c381c,0x381c381c,0x381c0000,0x391c3838,0x38383838,0x38380ee0,
      0x381c0ee0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xfffc,0x9c01ce,0x70f60000,0x70000e0,0x180,
      0x0,0x700,0x381c01c0,0x780001c,0x7ffc001c,0x381c0380,0x381c003c,0x0,0x3e07ffc,0xf800380,0x63031ffc,0x70387000,0x70387000,
      0x7000701c,0x703801c0,0x7070f0,0x7000701c,0x71b8701c,0x7000701c,0x70700038,0x1c07038,0x7e03e7c,0xf7801c0,0x1e000380,0x1c001c0,
      0x0,0x381c,0x381c3800,0x381c3800,0x380381c,0x383801c0,0xe01fe0,0x380739c,0x3838381c,0x381c381c,0x7000078,0x7003838,0xee03a5c,
      0x7c00fe0,0x78001c0,0x18001c0,0x0,0x1c003ef8,0x380,0x0,0x1000100,0x810,0x383000,0x70703ef8,0x1e003ef8,0x3ef80000,0x0,0x7c0,
      0x0,0x0,0x78000c,0xe1c03ef8,0x78001c0,0x1c0,0x1c200700,0x63001c0,0x18003f8,0x4e12,0x1c70,0xc0000,0x4c320000,0x180,0x0,0x3838,
      0x1980000,0x0,0xe38,0x700118,0x701e00,0x1ffc1ffc,0x1ffc1ffc,0x1ffc1ffc,0x7fe07000,0x70007000,0x70007000,0x1c001c0,0x1c001c0,
      0x703871b8,0x701c701c,0x701c701c,0x701c0e70,0x7c1c7038,0x70387038,0x703801c0,0x7fc0381c,0x381c381c,0x381c381c,0x381c381c,
      0x78e03800,0x38003800,0x38003800,0x1c001c0,0x1c001c0,0x381c3838,0x381c381c,0x381c381c,0x381c0000,0x3b1c3838,0x38383838,0x38380fe0,
      0x381c0fe0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1860,0x9c0186,0x707e0000,0x30000c0,0x180,
      0x0,0xe00,0x183801c0,0xf00001c,0xe0001c,0x181c0380,0x381c0038,0x0,0xfc0000,0x7e000000,0x61873c1e,0x70383800,0x70707000,0x7000381c,
      0x703801c0,0x707070,0x7000701c,0x70f83838,0x70003838,0x70780038,0x1c07038,0x7e03c3c,0xe3801c0,0x1c000380,0xe001c0,0x0,0x381c,
      0x381c3800,0x381c3800,0x380381c,0x383801c0,0xe01ef0,0x380739c,0x3838381c,0x381c381c,0x7000038,0x7003838,0xfe03e7c,0xfe007c0,
      0x70001c0,0x18001c0,0x0,0xe001ff0,0x380,0x0,0x1000100,0x162c,0x381800,0x30701ff0,0x1c001ff0,0x1ff00000,0x0,0x3c0,0x0,0x0,
      0x380018,0xe1c01ff0,0x70001c0,0x1c0,0x1c200700,0xff801c0,0x18000f0,0x63e6,0xe38,0x0,0x6c3e0000,0x0,0x0,0x3838,0x1980000,0x0,
      0x1c70,0xf0000c,0xf01c00,0x3c1e3c1e,0x3c1e3c1e,0x3c1e3c1c,0x70e03800,0x70007000,0x70007000,0x1c001c0,0x1c001c0,0x707070f8,
      0x38383838,0x38383838,0x38381c38,0x38387038,0x70387038,0x703801c0,0x7000381c,0x381c381c,0x381c381c,0x381c381c,0x70e03800,
      0x38003800,0x38003800,0x1c001c0,0x1c001c0,0x381c3838,0x381c381c,0x381c381c,0x381c0380,0x3e1c3838,0x38383838,0x383807c0,0x381c07c0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x18c0,0x9c0186,0x783c0000,0x38001c0,0x180,0x3800000,
      0x3800e00,0x1c3801c0,0x1e00003c,0xe00038,0x1c1c0780,0x381c0038,0x3800380,0x3c0000,0x78000000,0x61ff380e,0x70383808,0x70707000,
      0x7000381c,0x703801c0,0x40707078,0x7000701c,0x70f83838,0x70003838,0x70384038,0x1c07038,0x7e03c3c,0x1e3c01c0,0x3c000380,0xe001c0,
      0x0,0x383c,0x3c381c00,0x1c3c1c00,0x3801c3c,0x383801c0,0xe01c78,0x380739c,0x38381c38,0x3c381c3c,0x7000038,0x7003878,0x7c01e78,
      0x1ef007c0,0xf0001c0,0x18001c0,0x0,0xe000ee0,0x7800380,0xe380000,0x1001ff0,0x2242,0x40380c00,0x38700ee0,0x3c000ee0,0xee00000,
      0x0,0x0,0x0,0x0,0x380030,0xe1c00ee0,0xf0001c0,0x1c0,0xe200700,0xdd801c0,0x1800038,0x300c,0x71c,0x0,0x300c0000,0x0,0x0,0x3838,
      0x1980000,0x0,0x38e0,0xb0000c,0xb01c08,0x380e380e,0x380e380e,0x380e380e,0x70e03808,0x70007000,0x70007000,0x1c001c0,0x1c001c0,
      0x707070f8,0x38383838,0x38383838,0x3838381c,0x38387038,0x70387038,0x703801c0,0x7000381c,0x383c383c,0x383c383c,0x383c383c,
      0x70e01c00,0x1c001c00,0x1c001c00,0x1c001c0,0x1c001c0,0x1c383838,0x1c381c38,0x1c381c38,0x1c380380,0x1c383878,0x38783878,0x387807c0,
      0x3c3807c0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c0,0x18c0,0x10b801ce,0x3c3e0000,0x38001c0,0x180,
      0x3800000,0x3801c00,0x1e7801c0,0x3c002078,0xe02078,0x1c380700,0x1c3810f0,0x3800380,0x40000,0x40000380,0x307b380e,0x70701e18,
      0x70e07000,0x70001c1c,0x703801c0,0x60e0703c,0x7000701c,0x70f83c78,0x70003c70,0x703c70f0,0x1c03870,0x3c01c3c,0x3c1c01c0,0x78000380,
      0x7001c0,0x0,0x3c7c,0x3c381e18,0x1c7c1e0c,0x3801c3c,0x383801c0,0xe01c38,0x3c0739c,0x38381c38,0x3c381c3c,0x7001078,0x7803c78,
      0x7c01c38,0x1c780380,0x1e0001c0,0x18001c0,0x0,0x70c06c0,0x7000380,0xe300000,0x1000100,0x2142,0x70f00600,0x3c7006c0,0x780006c0,
      0x6c00000,0x0,0x0,0x0,0x0,0x10780060,0x73e206c0,0x1e0001c0,0x1c0,0x7240700,0x180c01c0,0x1800018,0x1818,0x30c,0x0,0x18180000,
      0x0,0x0,0x3c78,0x1980000,0x0,0x30c0,0x130000c,0x1301c18,0x380e380e,0x380e380e,0x380e380e,0x70e01e18,0x70007000,0x70007000,
      0x1c001c0,0x1c001c0,0x70e070f8,0x3c783c78,0x3c783c78,0x3c781008,0x7c783870,0x38703870,0x387001c0,0x70003a3c,0x3c7c3c7c,0x3c7c3c7c,
      0x3c7c3c7c,0x79f11e18,0x1e0c1e0c,0x1e0c1e0c,0x1c001c0,0x1c001c0,0x1c783838,0x1c381c38,0x1c381c38,0x1c380380,0x1c383c78,0x3c783c78,
      0x3c780380,0x3c380380,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c0,0x38c0,0x1ff800fc,0x1fee0000,
      0x1800180,0x180,0x3800000,0x3801c00,0xff01ffc,0x3ffc3ff0,0xe03ff0,0xff00700,0x1ff81fe0,0x3800380,0x0,0x380,0x3000780f,0x7ff00ff8,
      0x7fc07ff8,0x70000ffc,0x70381ffc,0x7fe0701c,0x7ff8701c,0x70781ff0,0x70001ff0,0x701c7ff0,0x1c01fe0,0x3c01c38,0x380e01c0,0x7ffc0380,
      0x7001c0,0x0,0x1fdc,0x3ff00ff0,0xffc0ffc,0x3800fdc,0x38383ffe,0xe01c3c,0x1fc739c,0x38380ff0,0x3ff00ffc,0x7001ff0,0x3f81fb8,
      0x7c01c38,0x3c3c0380,0x1ffc01c0,0x18001c0,0x0,0x3fc0380,0x7000380,0xc70718c,0x1000100,0x2244,0x7ff00200,0x1fff0380,0x7ffc0380,
      0x3800000,0x0,0x0,0x0,0x0,0x1ff000c0,0x7f7e0380,0x1ffc01c0,0x1c0,0x3fc3ffe,0x1c0,0x1800018,0x7e0,0x104,0x0,0x7e00000,0x7ffe,
      0x0,0x3fde,0x1980000,0x0,0x2080,0x3300018,0x3300ff0,0x780f780f,0x780f780f,0x780f780e,0xf0fe0ff8,0x7ff87ff8,0x7ff87ff8,0x1ffc1ffc,
      0x1ffc1ffc,0x7fc07078,0x1ff01ff0,0x1ff01ff0,0x1ff00000,0x7ff01fe0,0x1fe01fe0,0x1fe001c0,0x70003bf8,0x1fdc1fdc,0x1fdc1fdc,
      0x1fdc1fdc,0x3fbf0ff0,0xffc0ffc,0xffc0ffc,0x3ffe3ffe,0x3ffe3ffe,0xff03838,0xff00ff0,0xff00ff0,0xff00000,0x3ff01fb8,0x1fb81fb8,
      0x1fb80380,0x3ff00380,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c0,0x31c0,0x7e00078,0x7cf0000,0x1800180,
      0x0,0x3800000,0x3803800,0x3c01ffc,0x3ffc0fe0,0xe01fc0,0x3e00e00,0x7e00f80,0x3800380,0x0,0x380,0x18007007,0x7fc003f0,0x7f007ff8,
      0x700003f0,0x70381ffc,0x3f80701e,0x7ff8701c,0x707807c0,0x700007c0,0x701e1fc0,0x1c00fc0,0x3c01818,0x780f01c0,0x7ffc0380,0x3801c0,
      0x0,0xf9c,0x39e003e0,0x79c03f0,0x380079c,0x38383ffe,0xe01c1e,0x7c739c,0x383807e0,0x39e0079c,0x7000fc0,0x1f80f38,0x3801c38,
      0x781e0380,0x1ffc01c0,0x18001c0,0x0,0x1f80100,0xe000700,0x1c60718c,0x1000100,0x1e3c,0x1fc00100,0x7ff0100,0x7ffc0100,0x1000000,
      0x0,0x0,0x0,0x0,0xfc00080,0x3e3c0100,0x1ffc01c0,0x1c0,0xf83ffe,0x1c0,0x1800838,0x0,0x0,0x0,0x0,0x7ffe,0x0,0x3b9e,0x1980000,
      0x0,0x0,0x2300038,0x23003e0,0x70077007,0x70077007,0x70077007,0xe0fe03f0,0x7ff87ff8,0x7ff87ff8,0x1ffc1ffc,0x1ffc1ffc,0x7f007078,
      0x7c007c0,0x7c007c0,0x7c00000,0xc7c00fc0,0xfc00fc0,0xfc001c0,0x700039f0,0xf9c0f9c,0xf9c0f9c,0xf9c0f9c,0x1f1e03e0,0x3f003f0,
      0x3f003f0,0x3ffe3ffe,0x3ffe3ffe,0x7e03838,0x7e007e0,0x7e007e0,0x7e00000,0x63e00f38,0xf380f38,0xf380380,0x39e00380,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x800000,0x0,0xc00300,0x0,0x3000000,0x3800,0x0,0x0,0x0,0x0,
      0x0,0x300,0x0,0x0,0x1c000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xe0,0x0,0x0,0x0,0x0,0x380,0x3801c0,0x0,0x0,0x0,0x0,0x1c,0x0,0xe00000,
      0x0,0x0,0x3800001c,0x0,0x0,0x0,0x700,0x1c0,0x18001c0,0x0,0x0,0xe000700,0x18600000,0x1000100,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x200000,0x0,0x1800ff0,0x0,0x0,0x0,0x0,0x0,0x0,0x3800,0x1980000,0x1800000,0x0,0x6300070,0x6300000,0x0,
      0x0,0x0,0xc0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xc0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x40000000,
      0x0,0x700,0x38000700,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x800000,0x0,0xc00300,0x0,0x7000000,
      0x7000,0x0,0x0,0x0,0x0,0x0,0x700,0x0,0x0,0xf040000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x78,0x0,0x0,0x0,0x0,0x3f0,0x1c0fc0,0x0,0x0,
      0x0,0x0,0x1c,0x0,0xe00000,0x0,0x0,0x3800001c,0x0,0x0,0x0,0x700,0x1e0,0x18003c0,0x0,0x0,0xc000700,0x18c00000,0x1000000,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x200000,0x0,0x18007e0,0x0,0x0,0x0,0x0,0x0,0x0,0x3800,0x1980000,0xc00000,
      0x0,0x7f800e0,0x7f80000,0x0,0x0,0x0,0x60,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x60,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x700,0x38000700,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x800000,
      0x0,0x600600,0x0,0x6000000,0x0,0x0,0x0,0x0,0x0,0x0,0x600,0x0,0x0,0x7fc0000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x30,0x0,0x0,0x0,0x0,
      0x3f0,0xfc0,0x0,0x0,0x0,0x0,0x838,0x0,0x1e00000,0x0,0x0,0x3800001c,0x0,0x0,0x0,0xf00,0xfc,0x1801f80,0x0,0x0,0x8008e00,0x30c00000,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x200000,0x0,0x1800000,0x0,0x0,0x0,0x0,0x0,0x0,0x3800,0x1980000,0xc00000,
      0x0,0x3001c0,0x300000,0x0,0x0,0x0,0x60,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x60,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0xf00,0x38000f00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x800000,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xfc0000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0xff0,0x0,0x1fc00000,0x0,0x0,0x3800001c,0x0,0x0,0x0,0x3e00,0x7c,0x1801f00,0x0,0x0,0x800fe00,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x200000,0x0,0x1800000,0x0,0x0,0x0,0x0,0x0,0x0,0x3800,0x0,0x7c00000,0x0,0x3001fc,0x300000,
      0x0,0x0,0x0,0x3e0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3e0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x3e00,0x38003e00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xfff8,0x0,0x0,0x0,0x7e0,0x0,0x1f000000,
      0x0,0x0,0x3800001c,0x0,0x0,0x0,0x3c00,0x0,0x1800000,0x0,0x0,0x7800,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3800,0x0,0x7800000,0x0,0x0,0x0,0x0,0x0,0x0,0x3c0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c00,0x38003c00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xfff8,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1800000,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0 };

    // Definition of a 19x38 font.
    const unsigned int font19x38[19*38*256/32] = {
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c380000,0x0,0x1c380,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3800007,0x3c003,0x86000000,
      0x1e00000,0x3,0x80000700,0x3c00000,0x380000,0x70003c00,0x0,0xe1800e,0x1c00,0xf000e18,0x0,0x0,0x700000e0,0x780000,0x7000,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xe700000,0x0,0xe700,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x38e0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c0000e,0x7e003,0xe60071c0,0x7f80000,0x1,0xc0000e00,0x7e0038e,0x1c0000,
      0xe0007e00,0x38e00000,0xf98007,0x3800,0x1f800f98,0x1c70000,0x0,0x380001c0,0xfc0071,0xc000e000,0x0,0x0,0x0,0x0,0x3e00000,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x7e00000,0x0,0x7e00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x38e0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xe0001c,0xe7006,0x7c0071c0,0xe180000,0x0,0xe0001c00,0xe70038e,0xe0001,0xc000e700,0x38e00000,
      0x19f0003,0x80007000,0x39c019f0,0x1c70000,0x0,0x1c000380,0x1ce0071,0xc001c000,0x0,0x0,0x0,0x0,0x7f00000,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c00000,
      0x0,0x3c00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x38e0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x700038,0x1c3806,0x3c0071c0,0xc0c0000,0x0,0x70003800,0x1c38038e,0x70003,0x8001c380,0x38e00000,0x18f0001,0xc000e000,
      0x70e018f0,0x1c70000,0x0,0xe000700,0x3870071,0xc0038000,0x0,0x0,0x0,0x0,0xe380000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0xe000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x60000000,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c38,0x0,0x1,0xc3800000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c00000,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0xc0c0000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xe000003,0x80018000,0x0,0xc180000,
      0xe,0x380,0x1800000,0xe00000,0x38001800,0x0,0x38,0xe00,0x6000000,0x0,0x1,0xc0000070,0x300000,0x3800,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x600,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7000000,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xf0000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x78c00,0xc30,
      0x0,0x0,0xc3000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3800000,0x0,0x0,0x0,0xe0,0x1c000f,0xc0000000,0x0,0x0,
      0x0,0xc0c0000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7000007,0x3c003,0xc6000000,0xc180000,0x7,0x700,
      0x3c00000,0x700000,0x70003c00,0x0,0xf1801c,0x1c00,0xf000f18,0x0,0x0,0xe00000e0,0x780000,0x7000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x600,0x0,0x0,0x1c007000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xfe0000,0xfe000,0x0,0x3800000,0x700000,0x38,
      0x7,0xe000001c,0x1c00,0x1c00700,0x7fc0000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xf800e,0x3e0000,0x0,0x0,0x0,0x1e00000,0x0,0x1,
      0xf8000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7cc00,0x660,0x0,0x0,0x66000000,0x0,0x0,0x0,0x0,0x7,0x1c000000,0x0,0x0,0x0,0x3fe00000,
      0x0,0x0,0x7000000,0x0,0x0,0x0,0x3e0,0x7c001f,0xe0000000,0x0,0x0,0x0,0xe1c0000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x1f80,0x380000e,0x7e007,0xe60071c0,0xc180000,0x3,0x80000e00,0x7e0038e,0x380000,0xe0007e00,0x38e00f00,0x1f9800e,
      0x3800,0x1f801f98,0x1c70000,0x0,0x700001c0,0xfc0071,0xc000e007,0x38e0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x1c,0x1c7000,0x61c00600,0x1e00007e,0x70000,0x18003000,0x1800000,0x0,0x0,0x1c01f0,0x7e003f,0xc003f800,
      0x1e03ffc,0x7f01ff,0xfc03f000,0x7e000000,0x0,0x0,0xfc0,0x1e,0x7fe000,0x7e03fe00,0x3fff07ff,0xe007e038,0x383ffe0,0xff81c01,
      0xe1c000f8,0xf8f00e0,0xfc01ffc,0x3f00ff,0xc000fe07,0xfffc7007,0x1c007700,0x73c01ef,0x78ffff,0xfe0380,0xfe000,0x38000000,0x1800000,
      0x700000,0x38,0x1f,0xe000001c,0x1c00,0x1c00700,0x7fc0000,0x0,0x0,0x0,0x0,0x1c000000,0x0,0x0,0x0,0x3f800e,0x3f8000,0x0,0xfc0000,
      0x0,0x7f00000,0x0,0x1,0x98000000,0x7f00000,0x3ffe00,0xffff0,0x0,0x0,0x0,0x0,0x0,0xcf81f,0xee3807e0,0x0,0x0,0x7e03c01e,0x1c,
      0x0,0x1f800000,0xf0078038,0xfc007,0x1c000000,0xfe00000,0x0,0x0,0x3fe000f0,0xf,0xc001f800,0x6000000,0xffc000,0x0,0x1c0007e0,
      0x360,0x6c0010,0x70000700,0xf0001e,0x3c000,0x78000f00,0x7f800ff,0xf007e01f,0xff83fff0,0x7ffe0fff,0xc1fff03f,0xfe07ffc0,0xfff83fc0,
      0x7807007,0xe000fc00,0x1f8003f0,0x7e0000,0x1f867,0x70e00e,0x1c01c380,0x38f00787,0x3fe0,0x180000c,0x66006,0x7c0071c0,0xe380000,
      0x1,0x80000c00,0x660038e,0x180000,0xc0006600,0x38e0078e,0x19f0006,0x3000,0x198019f0,0x1c70000,0x0,0x30000180,0xcc0071,0xc000c007,
      0x38e0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c,0x1c7000,0x61800600,0x7f8001ff,0x70000,
      0x38003800,0x1800000,0x0,0x0,0x3807fc,0x1fe00ff,0xf00ffe00,0x3e03ffc,0xff81ff,0xfc07fc01,0xff800000,0x0,0x0,0x3fe0,0xfe001e,
      0x7ff801,0xff83ff80,0x3fff07ff,0xe01ff838,0x383ffe0,0xff81c03,0xc1c000f8,0xf8f80e0,0x3ff01fff,0xffc0ff,0xf003ff87,0xfffc7007,
      0x1e00f700,0x71c03c7,0x70ffff,0xfe01c0,0xfe000,0x7c000000,0xc00000,0x700000,0x38,0x3f,0xe000001c,0x1c00,0x1c00700,0x7fc0000,
      0x0,0x0,0x0,0x0,0x1c000000,0x0,0x0,0x0,0x3f800e,0x3f8000,0x0,0x3fe0000,0x0,0xff00000,0x0,0x3,0xc000000,0x1ffc0000,0xfffe00,
      0xffff0,0x0,0x0,0x0,0x0,0x0,0xc781f,0xee3803c0,0x0,0x0,0x3c01c01c,0x1c,0xc000,0x7fc00000,0x70070038,0x3fe007,0x1c000000,0x1ff80000,
      0x0,0x0,0x3fe003fc,0x1f,0xe003fc00,0xc000000,0x3ffc000,0x0,0x7c000ff0,0x60,0xc0000,0x30000700,0xf0001e,0x3c000,0x78000f00,
      0x3f000ff,0xf01ff81f,0xff83fff0,0x7ffe0fff,0xc1fff03f,0xfe07ffc0,0xfff83ff8,0x7c0701f,0xf803ff00,0x7fe00ffc,0x1ff8000,0x7fe67,
      0x70e00e,0x1c01c380,0x38700707,0x7ff0,0xc00018,0xc3006,0x3c0071c0,0x7f00000,0x0,0xc0001800,0xc30038e,0xc0001,0x8000c300,0x38e003fc,
      0x18f0003,0x6000,0x30c018f0,0x1c70000,0x0,0x18000300,0x1860071,0xc0018007,0x38e0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c,0x1c7000,0xe1801fc0,0x618001ff,0x70000,0x30001800,0x21840000,0x0,0x0,0x380ffe,0x1fe00ff,
      0xfc0fff00,0x3e03ffc,0x1ff81ff,0xfc0ffe03,0xffc00000,0x0,0x0,0x7ff0,0x3ff803f,0x7ffc03,0xffc3ffc0,0x3fff07ff,0xe03ffc38,0x383ffe0,
      0xff81c07,0x81c000f8,0xf8f80e0,0x7ff81fff,0x81ffe0ff,0xf80fff87,0xfffc7007,0xe00e700,0x70e0387,0x80f0ffff,0xe001c0,0xe000,
      0xfe000000,0xe00000,0x700000,0x38,0x3c,0x1c,0x1c00,0x1c00700,0x1c0000,0x0,0x0,0x0,0x0,0x1c000000,0x0,0x0,0x0,0x78000e,0x3c000,
      0x0,0x7ff0000,0x0,0xf100000,0x0,0x7,0xe000000,0x7ffc0000,0x1fffe00,0xffff0,0x0,0x0,0x0,0x0,0x0,0x3,0xf780180,0x0,0x0,0x1801e03c,
      0x1c,0xc000,0xffc00000,0x780f0038,0x786000,0x7f00,0x18380000,0x0,0xfe00,0x30c,0x10,0x70020e00,0x1c000000,0x7f8c000,0x0,0x6c001c38,
      0x60,0xc0000,0x70000700,0x1f8003f,0x7e000,0xfc001f80,0x3f000ff,0xf03ffc1f,0xff83fff0,0x7ffe0fff,0xc1fff03f,0xfe07ffc0,0xfff83ffc,
      0x7c0703f,0xfc07ff80,0xfff01ffe,0x3ffc000,0xffec7,0x70e00e,0x1c01c380,0x38780f07,0xf070,0xe00038,0x1c3800,0x0,0x3e00000,0x0,
      0xe0003800,0x1c380000,0xe0003,0x8001c380,0x3e0,0x3,0x8000e000,0x70e00000,0x0,0x0,0x1c000700,0x3870000,0x38007,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c,0x1c7000,0xe3807ff0,0xc0c003c1,0x70000,0x70001c00,
      0x718e0000,0x0,0x0,0x700f1e,0x1ce00c0,0x3c0c0f80,0x7e03800,0x3e08000,0x381e0f03,0xc1e00000,0x0,0x0,0x7078,0x783c03f,0x701e07,
      0xc1c383e0,0x38000700,0x7c1c38,0x3801c00,0x381c0f,0x1c000fc,0x1f8f80e0,0x78781c07,0x81e1e0e0,0x780f0180,0xe007007,0xe00e380,
      0xe0f0783,0x80e0000e,0xe000e0,0xe001,0xef000000,0x0,0x700000,0x38,0x38,0x1c,0x0,0x700,0x1c0000,0x0,0x0,0x0,0x0,0x1c000000,
      0x0,0x0,0x0,0x70000e,0x1c000,0x0,0xf830000,0x0,0x1e000000,0x0,0x0,0x10000,0x780c0000,0x3e38000,0xe0,0x0,0x0,0x0,0x0,0x0,0x3,
      0xd580000,0x0,0x0,0xe038,0x1c,0xc000,0xf0400000,0x380e0038,0x702000,0x1ffc0,0xc0000,0x0,0x3ff80,0x606,0x0,0x30000600,0x0,
      0x7f8c000,0x0,0xc001818,0x60,0xc0003,0xe0000700,0x1f8003f,0x7e000,0xfc001f80,0x73801ee,0x7c1c1c,0x38000,0x70000e00,0xe0001,
      0xc0003800,0x700383e,0x7c0703c,0x3c078780,0xf0f01e1e,0x3c3c000,0xf0f87,0x70e00e,0x1c01c380,0x38380e07,0xe038,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0xff0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x1c,0x1c7000,0xc380fff0,0xc0c00380,0x70000,0x70001c00,0x3dbc0070,0x0,0x0,0x701e0f,0xe0000,0x1e000380,
      0x6e03800,0x7800000,0x781c0707,0x80e00000,0x0,0x0,0x4038,0xe00c03f,0x700e07,0x4380f0,0x38000700,0x700438,0x3801c00,0x381c0e,
      0x1c000ec,0x1b8fc0e0,0xf03c1c03,0xc3c0f0e0,0x3c1e0000,0xe007007,0xe00e380,0xe070703,0xc1e0001e,0xe000e0,0xe001,0xc7000000,
      0x0,0x700000,0x38,0x38,0x1c,0x0,0x700,0x1c0000,0x0,0x0,0x0,0x0,0x1c000000,0x0,0x0,0x0,0x70000e,0x1c000,0x0,0xe010000,0x0,
      0x1c000000,0x10,0x20000,0x6c000,0xf0000000,0x3838000,0x1e0,0x0,0xf000f,0xf1e00,0x78f00000,0x0,0x3,0xdd80000,0x0,0x0,0xf078,
      0x0,0xc001,0xe0000000,0x1c1c0038,0x700000,0x3c1e0,0xc0000,0x0,0x783c0,0x606,0x0,0x30000e00,0x0,0xff8c000,0x0,0xc00300c,0x60,
      0xc0003,0xe0000000,0x1f8003f,0x7e000,0xfc001f80,0x73801ce,0x70041c,0x38000,0x70000e00,0xe0001,0xc0003800,0x700380f,0x7e07078,
      0x1e0f03c1,0xe0783c0f,0x781e000,0x1c0787,0x70e00e,0x1c01c380,0x383c1e07,0xff00e038,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x878,
      0x0,0x0,0x0,0x7,0x80000080,0x0,0x0,0x7,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c,
      0x1c7000,0xc301e630,0xc0c00380,0x70000,0xe0000e00,0xff00070,0x0,0x0,0xe01c07,0xe0000,0xe000380,0xce03800,0x7000000,0x701c0707,
      0x600000,0x0,0x4000010,0x38,0x1c00e07f,0x80700e0e,0x38070,0x38000700,0xe00038,0x3801c00,0x381c1c,0x1c000ec,0x1b8ec0e0,0xe01c1c01,
      0xc38070e0,0x1c1c0000,0xe007007,0x701c380,0xe078e01,0xc1c0003c,0xe00070,0xe003,0x83800000,0x7f,0x71f000,0x3e003e38,0x3f007ff,
      0xe01f1c1c,0x7801fc00,0x3fc00701,0xe01c0077,0x8f071e00,0xf801c7c,0x7c700e,0x3e01fc03,0xfff8380e,0xe007700,0x73c0787,0x387ffc,
      0x70000e,0x1c000,0x0,0xe000000,0x0,0x1c000000,0x10,0x20000,0xc2000,0xe0000000,0x3838000,0x3c0,0x0,0xf000f,0x78e00,0x70e00000,
      0x0,0x3,0xc980fe0,0x1f0,0xf8000007,0xffc07070,0x0,0x3f801,0xc0000000,0x1e3c0038,0x700000,0x70070,0x7fc0000,0x0,0xe00e0,0x606,
      0x1c0000,0x70007c00,0x380e,0xff8c000,0x0,0xc00300c,0x60,0xc0000,0x70000000,0x3fc007f,0x800ff001,0xfe003fc0,0x73801ce,0xe0001c,
      0x38000,0x70000e00,0xe0001,0xc0003800,0x7003807,0x7607070,0xe0e01c1,0xc0383807,0x700e000,0x1c0387,0x70e00e,0x1c01c380,0x381c1c07,
      0xffc0e0f8,0x3f8007f,0xfe001,0xfc003f80,0x7f007e3,0xe003e001,0xf8003f00,0x7e000fc,0xfe001f,0xc003f800,0x7f00003c,0x38f0007,
      0xc000f800,0x1f0003e0,0x7c0007,0x8003f0c3,0x80e0701c,0xe0381c0,0x70700387,0x1f01c00e,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c,0x1c701f,0xfff1c600,0xc0c00380,0x70000,0xe0000e00,0x3c00070,0x0,0x0,0xe03c07,
      0x800e0000,0xe000380,0x1ce03800,0x7000000,0x701c0707,0x7003c0,0x780000,0x3c00001e,0x38,0x18006073,0x80700e0e,0x38070,0x38000700,
      0xe00038,0x3801c00,0x381c38,0x1c000ee,0x3b8ee0e1,0xe01e1c01,0xc78078e0,0x1c1c0000,0xe007007,0x701c387,0xe03de00,0xe3800038,
      0xe00070,0xe007,0x1c00000,0x1ff,0xc077f801,0xff807fb8,0xff807ff,0xe03fdc1d,0xfc01fc00,0x3fc00703,0xc01c007f,0xdf877f00,0x3fe01dfe,
      0xff700e,0xff07ff03,0xfff8380e,0x700f700,0x71e0f03,0x80707ffc,0x70000e,0x1c000,0x0,0x1c000008,0x0,0x1c000000,0x10,0x20000,
      0x82000,0xe0000000,0x7038000,0x80000380,0x2000040,0x7000e,0x38700,0xf1e00000,0x0,0x3,0xc183ff8,0x3fd,0xfc008007,0xffc038e0,
      0x0,0xffc01,0xc0008008,0xe380038,0x380000,0xe3e38,0x1ffc0040,0x80000000,0x1cfc70,0x606,0x1c0000,0xe0007c00,0x380e,0xff8c000,
      0x0,0xc00300c,0x8100060,0xc0000,0x30000700,0x39c0073,0x800e7001,0xce0039c0,0x73801ce,0xe0001c,0x38000,0x70000e00,0xe0001,
      0xc0003800,0x7003807,0x77070f0,0xf1e01e3,0xc03c7807,0x8f00f080,0x83c0787,0x70e00e,0x1c01c380,0x380e3807,0xffe0e1c0,0xffe01ff,
      0xc03ff807,0xff00ffe0,0x1ffc0ff7,0xf01ff807,0xfc00ff80,0x1ff003fe,0xfe001f,0xc003f800,0x7f0003fc,0x3bf801f,0xf003fe00,0x7fc00ff8,
      0x1ff0007,0x8007fd83,0x80e0701c,0xe0381c0,0x70380707,0x7f80e01c,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x1c,0x1c701f,0xfff1c600,0x618081c0,0x70000,0xe0000e00,0x3c00070,0x0,0x0,0xe03803,0x800e0000,0xe000380,0x18e03800,
      0xf000000,0xf01c0707,0x7003c0,0x780000,0xfc00001f,0x80000078,0x301e6073,0x80700e1c,0x38038,0x38000700,0x1c00038,0x3801c00,
      0x381c70,0x1c000e6,0x338ee0e1,0xc00e1c01,0xc70038e0,0x1c1c0000,0xe007007,0x701c387,0xe01dc00,0xf7800078,0xe00070,0xe00e,0xe00000,
      0x3ff,0xe07ffc03,0xffc0fff8,0x1ffc07ff,0xe07ffc1d,0xfe01fc00,0x3fc00707,0x801c007f,0xdf877f80,0x7ff01fff,0x1fff00e,0xff07ff03,
      0xfff8380e,0x700e380,0xe0e0e03,0x80707ffc,0x70000e,0x1c000,0x0,0x7ffc001c,0x0,0x1c000000,0x10,0x20000,0x82000,0xe0000000,
      0x7038001,0xc0000780,0x70000e0,0x3800e,0x38700,0xe1c00000,0x0,0x3,0xc183ff8,0x7ff,0xfc01c007,0xffc03de0,0x0,0x1ffc01,0xc001c01c,
      0xf780038,0x3c0000,0xcff18,0x380c00c1,0x80000000,0x18fe30,0x30c,0x1c0001,0xc0000e00,0x380e,0xff8c000,0x0,0xc00300c,0xc180060,
      0xc0000,0x30000700,0x39c0073,0x800e7001,0xce0039c0,0xe1c038e,0x1c0001c,0x38000,0x70000e00,0xe0001,0xc0003800,0x7003803,0x877070e0,
      0x71c00e3,0x801c7003,0x8e0071c0,0x1c380fc7,0x70e00e,0x1c01c380,0x380f7807,0x1e0e380,0x1fff03ff,0xe07ffc0f,0xff81fff0,0x3ffe0fff,
      0xf03ffc0f,0xfe01ffc0,0x3ff807ff,0xfe001f,0xc003f800,0x7f0007fe,0x3bfc03f,0xf807ff00,0xffe01ffc,0x3ff8007,0x800fff83,0x80e0701c,
      0xe0381c0,0x70380707,0xffc0e01c,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c,0x1c701f,
      0xfff1c600,0x7f8381e0,0x70000,0xc0000600,0xff00070,0x0,0x0,0x1c03803,0x800e0000,0xe000f00,0x38e03fe0,0xe000000,0xe00e0e07,
      0x7003c0,0x780007,0xf0ffff87,0xf00000f0,0x307fe0f3,0xc0703c1c,0x38038,0x38000700,0x1c00038,0x3801c00,0x381ce0,0x1c000e6,0x338e70e1,
      0xc00e1c01,0xc70038e0,0x3c1e0000,0xe007007,0x783c38f,0x8e01fc00,0x770000f0,0xe00038,0xe01c,0x700000,0x381,0xe07c1e07,0xc0c1e0f8,
      0x3c1e0038,0xf07c1f,0xe001c00,0x1c0070f,0x1c0079,0xf3c7c380,0xf0781f07,0x83c1f00f,0xc10f0300,0x1c00380e,0x700e380,0xe0f1e03,
      0xc0f00078,0x70000e,0x1c000,0x0,0xfff8003e,0x0,0x3c000000,0x10,0x20000,0xc6000,0xf0000000,0x7038003,0xe0000f00,0xf8001f0,
      0x3801c,0x18300,0xe1800000,0x0,0x3,0xc187818,0x70f,0x9e03e000,0x7801dc0,0x1c,0x3cc401,0xc000efb8,0x7f7f0038,0x3f0000,0x1ce11c,
      0x300c01c3,0x80000000,0x38c638,0x3fc,0x1c0003,0x80000600,0x380e,0xff8c000,0x0,0xc00300c,0xe1c0060,0xc0010,0x70000700,0x79e00f3,
      0xc01e7803,0xcf0079e0,0xe1c038e,0x1c0001c,0x38000,0x70000e00,0xe0001,0xc0003800,0x7003803,0x873870e0,0x71c00e3,0x801c7003,
      0x8e0070e0,0x38381dc7,0x70e00e,0x1c01c380,0x38077007,0xf0e700,0x1c0f0381,0xe0703c0e,0x781c0f0,0x381e083e,0x787c0c1e,0xf03c1e0,
      0x783c0f07,0x800e0001,0xc0003800,0x7000fff,0x3e1c078,0x3c0f0781,0xe0f03c1e,0x783c000,0x1e0f03,0x80e0701c,0xe0381c0,0x70380f07,
      0xc1e0e03c,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c,0x1,0x8701c600,0x1e0f01e0,0x1,
      0xc0000700,0x3dbc0070,0x0,0x0,0x1c03803,0x800e0000,0x1e01fe00,0x70e03ff8,0xe3e0001,0xe007fc07,0x80f003c0,0x78001f,0xc0ffff81,
      0xfc0001e0,0x30e1e0e1,0xc07ff81c,0x38038,0x3ffe07ff,0xc1c0003f,0xff801c00,0x381de0,0x1c000e7,0x738e70e1,0xc00e1c03,0xc70038e0,
      0x780f8000,0xe007007,0x383838d,0x8e00f800,0x7f0000e0,0xe00038,0xe000,0x0,0x200,0xf0780e07,0x8041c078,0x380e0038,0xe03c1e,
      0xf001c00,0x1c0071e,0x1c0070,0xe1c783c0,0xe0381e03,0x8380f00f,0xe0000,0x1c00380e,0x381c380,0xe07bc01,0xc0e00078,0x70000e,
      0x1c000,0x0,0x1c000061,0x0,0x38000000,0x10,0x20000,0x7c000,0x7c000000,0x703fc06,0x10000e00,0x18400308,0x1801c,0x1c381,0xc3800000,
      0x0,0x0,0x7000,0xe0f,0xe061000,0x7801fc0,0x1c,0x38c001,0xc0007ff0,0x7fff0038,0x77c000,0x19c00c,0x301c0387,0x0,0x30c618,0xf0,
      0x1c0007,0x600,0x380e,0x7f8c007,0x80000000,0xc001818,0x70e03fc,0x387f871f,0xe0e00700,0x70e00e1,0xc01c3803,0x870070e0,0xe1c038f,
      0xe1c0001f,0xff03ffe0,0x7ffc0fff,0x800e0001,0xc0003800,0x7003803,0x873870e0,0x71c00e3,0x801c7003,0x8e007070,0x703839c7,0x70e00e,
      0x1c01c380,0x3807f007,0x70e700,0x10078200,0xf0401e08,0x3c10078,0x200f001c,0x3878041c,0x70380e0,0x701c0e03,0x800e0001,0xc0003800,
      0x7001e0f,0x3c1e070,0x1c0e0381,0xc070380e,0x701c000,0x1c0f03,0x80e0701c,0xe0381c0,0x701c0e07,0x80e07038,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c,0x3,0x8600e600,0x7803f0,0x1,0xc0000700,0x718e0070,0x0,0x0,0x38038c3,
      0x800e0000,0x3c01f800,0x60e03ffc,0xeff8001,0xc001f003,0xc1f003c0,0x7800fe,0xffff80,0x3f8003c0,0x60c0e0e1,0xc07fe01c,0x38038,
      0x3ffe07ff,0xc1c07e3f,0xff801c00,0x381fe0,0x1c000e3,0x638e30e1,0xc00e1c07,0x870038ff,0xf00ff800,0xe007007,0x38381cd,0x9c007000,
      0x3e0001e0,0xe0001c,0xe000,0x0,0x0,0x70780f0f,0x3c078,0x70070038,0x1e03c1c,0x7001c00,0x1c0073c,0x1c0070,0xe1c701c1,0xe03c1e03,
      0xc780f00f,0xe0000,0x1c00380e,0x381c387,0xe03f801,0xc0e000f0,0x70000e,0x1c007,0xe0100000,0x1c0000cd,0x80000003,0xffc00000,
      0x3ff,0x807ff000,0xe0,0x7fc00060,0x703fc0c,0xd8001e00,0x3360066c,0x1c018,0xc181,0x83000000,0x0,0x0,0x7000,0x300e07,0xe0cd800,
      0xf000f80,0x1c,0x78c00f,0xff0038e0,0x3e00038,0xe1e000,0x19800c,0x383c070e,0x7fffc00,0x30fc18,0x0,0xffff80e,0x20e00,0x380e,
      0x7f8c007,0x80000000,0xc001c38,0x38703ff,0xf87fff0f,0xcfe00f00,0x70e00e1,0xc01c3803,0x870070e0,0x1e1e078f,0xe1c0001f,0xff03ffe0,
      0x7ffc0fff,0x800e0001,0xc0003800,0x700ff83,0x871870e0,0x71c00e3,0x801c7003,0x8e007038,0xe03871c7,0x70e00e,0x1c01c380,0x3803e007,
      0x70e700,0x38000,0x70000e00,0x1c00038,0x7001c,0x38f00038,0x3870070,0xe00e1c01,0xc00e0001,0xc0003800,0x7001c07,0x380e0f0,0x1e1e03c3,
      0xc078780f,0xf01e000,0x3c0f03,0x80e0701c,0xe0381c0,0x701c0e07,0x80f07038,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c,0x3,0x8600ff00,0x1e00778,0x38000001,0xc0000700,0x21843fff,0xe0000000,0x0,0x38039e3,0x800e0000,
      0x7c01fe00,0xe0e0203e,0xeffc001,0xc00ffe03,0xff700000,0x7f0,0x0,0x7f00380,0x618060e1,0xc07ffc1c,0x38038,0x3ffe07ff,0xc1c07e3f,
      0xff801c00,0x381ff0,0x1c000e3,0x638e38e1,0xc00e1fff,0x870038ff,0xc003fe00,0xe007007,0x38381cd,0x9c00f800,0x3e0003c0,0xe0001c,
      0xe000,0x0,0x0,0x7070070e,0x38038,0x70070038,0x1c01c1c,0x7001c00,0x1c00778,0x1c0070,0xe1c701c1,0xc01c1c01,0xc700700e,0xfc000,
      0x1c00380e,0x381c3c7,0x1e01f001,0xe1e001e0,0xf0000e,0x1e01f,0xf8300000,0x1c00019c,0xc0000003,0xffc00000,0x10,0x20000,0x700,
      0x1ff000c0,0x703fc19,0xcc003c00,0x67300ce6,0xc038,0xc181,0x83000000,0x0,0x0,0x7e00,0x180e07,0xe19cc00,0x1e000f80,0x1c,0x70c00f,
      0xff007070,0x3e00038,0xe0f000,0x19800c,0x1fec0e1c,0x7fffc00,0x30f818,0x0,0xffff81f,0xf003fc00,0x380e,0x3f8c007,0x80000000,
      0x7f800ff0,0x1c3803f,0xe007fc00,0xff800e00,0x70e00e1,0xc01c3803,0x870070e0,0x1c0e070f,0xe1c0001f,0xff03ffe0,0x7ffc0fff,0x800e0001,
      0xc0003800,0x700ff83,0x871c70e0,0x71c00e3,0x801c7003,0x8e00701d,0xc038e1c7,0x70e00e,0x1c01c380,0x3803e007,0x70e3c0,0x38000,
      0x70000e00,0x1c00038,0x7001c,0x38e00038,0x3870070,0xe00e1c01,0xc00e0001,0xc0003800,0x7003c07,0x8380e0e0,0xe1c01c3,0x80387007,
      0xe00e1ff,0xfe381b83,0x80e0701c,0xe0381c0,0x701e1e07,0x707878,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x1c,0x3,0xe007fe0,0x7800e3c,0x38000001,0xc0000700,0x1803fff,0xe0000000,0x0,0x70039c3,0x800e0000,0xf8000f80,
      0xc0e0000e,0xf83c003,0xc01e0f01,0xff700000,0x7c0,0x0,0x1f00780,0x618061c0,0xe0701e1c,0x38038,0x38000700,0x1c07e38,0x3801c00,
      0x381e78,0x1c000e3,0xe38e18e1,0xc00e1fff,0x70038ff,0xe0007f80,0xe007007,0x1c701dd,0x9c00f800,0x1c000780,0xe0000e,0xe000,0x0,
      0x7f,0xf070070e,0x38038,0x7fff0038,0x1c01c1c,0x7001c00,0x1c007f8,0x1c0070,0xe1c701c1,0xc01c1c01,0xc700700e,0x7fc00,0x1c00380e,
      0x1c381c7,0x1c01f000,0xe1c001c0,0xfe0000e,0xfe1f,0xfff00000,0x7ff003fc,0xe0000003,0xffc00000,0x10,0x20000,0x3800,0x3fc0180,
      0x703803f,0xce007800,0xff381fe7,0x30,0x0,0xc0,0x0,0x0,0x3fe0,0xc0e07,0xfe3fce00,0x1c000700,0x1c,0x70c00f,0xff006030,0x1c00000,
      0xe07800,0x19800c,0xfcc1c38,0x7fffc00,0x30d818,0x0,0xffff81f,0xf001f800,0x380e,0xf8c007,0x80000000,0x7f8007e0,0xe1c3fe,0x7fc00f,
      0xf8001e00,0xe0701c0,0xe0381c07,0x380e070,0x1c0e070e,0x1c0001c,0x38000,0x70000e00,0xe0001,0xc0003800,0x700ff83,0x870c70e0,
      0x71c00e3,0x801c7003,0x8e00700f,0x8038c1c7,0x70e00e,0x1c01c380,0x3801c007,0xf0e3e0,0x3ff807f,0xf00ffe01,0xffc03ff8,0x7ff03ff,
      0xf8e0003f,0xff87fff0,0xfffe1fff,0xc00e0001,0xc0003800,0x7003803,0x8380e0e0,0xe1c01c3,0x80387007,0xe00e1ff,0xfe383383,0x80e0701c,
      0xe0381c0,0x700e1c07,0x703870,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c,0x3,0xc000ff0,
      0x3c1e1c1c,0x38000001,0xc0000700,0x1803fff,0xe0000007,0xf8000000,0x7003803,0x800e0001,0xf0000381,0xc0e00007,0xf01e003,0x801c0700,
      0x7c700000,0x7c0,0x0,0x1f00700,0x618061c0,0xe0700e1c,0x38038,0x38000700,0x1c00e38,0x3801c00,0x381e38,0x1c000e1,0xc38e1ce1,
      0xc00e1ffc,0x70038e0,0xf0000780,0xe007007,0x1c701dd,0xdc01fc00,0x1c000780,0xe0000e,0xe000,0x0,0x1ff,0xf070070e,0x38038,0x7fff0038,
      0x1c01c1c,0x7001c00,0x1c007f8,0x1c0070,0xe1c701c1,0xc01c1c01,0xc700700e,0x3ff00,0x1c00380e,0x1c381cd,0x9c00e000,0xe1c003c0,
      0xf80000e,0x3e18,0x3ff00000,0xffe007fd,0xf0000000,0x38000000,0x10,0x20000,0x1c000,0x3c0300,0x703807f,0xdf007801,0xff7c3fef,
      0x80000000,0x0,0x3e0,0x7ffe7ff,0xff000000,0x1ff8,0x60e07,0xfe7fdf00,0x3c000700,0x1c,0x70c001,0xc0006030,0x7fff0000,0xf03800,
      0x19800c,0x1c38,0x1c07,0xf830cc18,0x0,0x1c0000,0x0,0x380e,0x18c007,0x80000000,0x0,0xe1cfe0,0x1fc003f,0x80003c00,0xe0701c0,
      0xe0381c07,0x380e070,0x1c0e070e,0x1c0001c,0x38000,0x70000e00,0xe0001,0xc0003800,0x7003803,0x870e70e0,0x71c00e3,0x801c7003,
      0x8e007007,0x3981c7,0x70e00e,0x1c01c380,0x3801c007,0x1e0e0f8,0xfff81ff,0xf03ffe07,0xffc0fff8,0x1fff07ff,0xf8e0003f,0xff87fff0,
      0xfffe1fff,0xc00e0001,0xc0003800,0x7003803,0x8380e0e0,0xe1c01c3,0x80387007,0xe00e1ff,0xfe386383,0x80e0701c,0xe0381c0,0x700e1c07,
      0x703870,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c,0x7f,0xffc00678,0x707f9c1e,0x38000001,
      0xc0000700,0x70,0x7,0xf8000000,0xe003803,0x800e0003,0xe00001c3,0x80e00007,0xe00e007,0x80380380,0x700000,0x7f0,0x0,0x7f00700,
      0x618061ff,0xe070071c,0x38038,0x38000700,0x1c00e38,0x3801c00,0x381c3c,0x1c000e1,0xc38e1ce1,0xc00e1c00,0x70038e0,0x700003c0,
      0xe007007,0x1c701d8,0xdc03dc00,0x1c000f00,0xe00007,0xe000,0x0,0x3ff,0xf070070e,0x38038,0x7fff0038,0x1c01c1c,0x7001c00,0x1c007fc,
      0x1c0070,0xe1c701c1,0xc01c1c01,0xc700700e,0x3f00,0x1c00380e,0x1c381cd,0x9c01f000,0x73800780,0xfe0000e,0xfe10,0x7c00000,0x1c000ffb,
      0xf8000000,0x38000000,0x10,0x20000,0x20000,0x1e0700,0x70380ff,0xbf80f003,0xfefe7fdf,0xc0000000,0x0,0x3f0,0x7ffe7ff,0xff000000,
      0x1f8,0x30e07,0xfeffbf80,0x78000700,0x1c,0x70c001,0xc0006030,0x7fff0000,0x783800,0x1ce11c,0xe1c,0x1c07,0xf838ce38,0x0,0x1c0000,
      0x0,0x380e,0x18c000,0x0,0x0,0x1c38c00,0x1800030,0x7800,0xfff01ff,0xe03ffc07,0xff80fff0,0x3fff0ffe,0x1c0001c,0x38000,0x70000e00,
      0xe0001,0xc0003800,0x7003803,0x870e70e0,0x71c00e3,0x801c7003,0x8e00700f,0x803b81c7,0x70e00e,0x1c01c380,0x3801c007,0xffe0e03c,
      0x1fff83ff,0xf07ffe0f,0xffc1fff8,0x3fff0fff,0xf8e0003f,0xff87fff0,0xfffe1fff,0xc00e0001,0xc0003800,0x7003803,0x8380e0e0,0xe1c01c3,
      0x80387007,0xe00e000,0x38c383,0x80e0701c,0xe0381c0,0x70073807,0x701ce0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7f,0xffc0063c,0x40619c0f,0x30000001,0xc0000700,0x70,0x7,0xf8000000,0xe003803,0x800e0007,0xc00001c3,
      0xfffc0007,0xe00e007,0x380380,0xf00000,0xfe,0xffff80,0x3f800700,0x618063ff,0xf070071c,0x38038,0x38000700,0x1c00e38,0x3801c00,
      0x381c1e,0x1c000e0,0x38e0ee1,0xc00e1c00,0x70038e0,0x380001c0,0xe007007,0x1ef01d8,0xdc038e00,0x1c001e00,0xe00007,0xe000,0x0,
      0x7c0,0x7070070e,0x38038,0x70000038,0x1c01c1c,0x7001c00,0x1c0079e,0x1c0070,0xe1c701c1,0xc01c1c01,0xc700700e,0x780,0x1c00380e,
      0xe701cd,0x9c01f000,0x73800f00,0xe0000e,0xe000,0x0,0x1c0007f7,0xf0000000,0x70000000,0x10,0x20000,0x0,0xe0e00,0x703807f,0x7f01e001,
      0xfdfc3fbf,0x80000000,0x0,0x7f0,0x0,0x0,0x3c,0x18e07,0x7f7f00,0xf0000700,0x1c,0x70c001,0xc0007070,0x1c00000,0x3e7000,0xcff18,
      0x3ffc070e,0x1c07,0xf818c630,0x0,0x1c0000,0x0,0x380e,0x18c000,0x0,0x3ffc,0x3870000,0xe000fc00,0x380f000,0x1fff83ff,0xf07ffe0f,
      0xffc1fff8,0x3fff0ffe,0x1c0001c,0x38000,0x70000e00,0xe0001,0xc0003800,0x7003803,0x870770e0,0x71c00e3,0x801c7003,0x8e00701d,
      0xc03f01c7,0x70e00e,0x1c01c380,0x3801c007,0xffc0e01c,0x3e0387c0,0x70f80e1f,0x1c3e038,0x7c071e1c,0xe00038,0x70000,0xe0001c00,
      0xe0001,0xc0003800,0x7003803,0x8380e0e0,0xe1c01c3,0x80387007,0xe00e000,0x398383,0x80e0701c,0xe0381c0,0x70073807,0x701ce0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7f,0xffc0061c,0xc0dc07,0xf0000001,0xc0000700,
      0x70,0x0,0x0,0x1c003c07,0x800e000f,0x1c3,0xfffc0007,0xe00e007,0x380380,0xe00000,0x1f,0xc0ffff81,0xfc000700,0x618063ff,0xf070070e,
      0x38070,0x38000700,0xe00e38,0x3801c00,0x381c0e,0x1c000e0,0x38e0ee1,0xe01e1c00,0x78078e0,0x380001c0,0xe007007,0xee01f8,0xfc078f00,
      0x1c001c00,0xe00003,0x8000e000,0x0,0x700,0x7070070e,0x38038,0x70000038,0x1c01c1c,0x7001c00,0x1c0070e,0x1c0070,0xe1c701c1,
      0xc01c1c01,0xc700700e,0x380,0x1c00380e,0xe700ed,0xb803f800,0x77800f00,0x70000e,0x1c000,0x0,0xe0003f7,0xe0000000,0x70000000,
      0x10,0x20000,0x1c0e0,0xe1c00,0x703803f,0x7e01c000,0xfdf81fbf,0x0,0x0,0x3f0,0x0,0x0,0x1c,0x1ce07,0x3f7e00,0xf0000700,0x1c,
      0x70c001,0xc00038e0,0x1c00038,0xf7000,0xe3e38,0x3ffc0387,0x1c00,0x1cc770,0x0,0x1c0000,0x0,0x380e,0x18c000,0x0,0x3ffc,0x70e0001,
      0xe001fe00,0x780e000,0x1fff83ff,0xf07ffe0f,0xffc1fff8,0x3fff0ffe,0xe0001c,0x38000,0x70000e00,0xe0001,0xc0003800,0x7003807,
      0x70770f0,0xf1e01e3,0xc03c7807,0x8f00f038,0xe03e03c7,0x70e00e,0x1c01c380,0x3801c007,0xff00e00e,0x38038700,0x70e00e1c,0x1c38038,
      0x70071c1c,0xe00038,0x70000,0xe0001c00,0xe0001,0xc0003800,0x7003803,0x8380e0e0,0xe1c01c3,0x80387007,0xe00e000,0x3b0383,0x80e0701c,
      0xe0381c0,0x70077807,0x701de0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x6,0x1c00061c,
      0xc0de03,0xe0000001,0xc0000700,0x70,0x0,0x0,0x1c001c07,0xe001e,0x1c3,0xfffc0007,0x600e00e,0x380380,0xe00000,0x7,0xf0ffff87,
      0xf0000000,0x60c0e380,0x7070070e,0x38070,0x38000700,0xe00e38,0x3801c00,0x381c0f,0x1c000e0,0x38e06e0,0xe01c1c00,0x38070e0,
      0x1c0001c0,0xe007007,0xee00f8,0xf80f0700,0x1c003c00,0xe00003,0x8000e000,0x0,0x700,0x70780f0f,0x3c078,0x70000038,0x1e03c1c,
      0x7001c00,0x1c0070f,0x1c0070,0xe1c701c1,0xe03c1e03,0xc780f00e,0x380,0x1c00380e,0xe700f8,0xf807bc00,0x3f001e00,0x70000e,0x1c000,
      0x0,0xe0001ff,0xc0000000,0x70000000,0x10,0x20000,0x33110,0xe0e00,0x383801f,0xfc03c000,0x7ff00ffe,0x0,0x0,0x3e0,0x0,0x0,0x1c,
      0x38e07,0x1ffc01,0xe0000700,0x1c,0x78c001,0xc0007ff0,0x1c00038,0x7c000,0x70070,0x1c3,0x80001c00,0xe00e0,0x0,0x1c0000,0x0,
      0x380e,0x18c000,0x0,0x0,0xe1c0001,0xe0010700,0x780e000,0x1c038380,0x70700e0e,0x1c1c038,0x78070e0e,0xe0001c,0x38000,0x70000e00,
      0xe0001,0xc0003800,0x7003807,0x7037070,0xe0e01c1,0xc0383807,0x700e070,0x701c0387,0x70e00e,0x1c01c380,0x3801c007,0xe00e,0x38038700,
      0x70e00e1c,0x1c38038,0x70071c1c,0xf00038,0x70000,0xe0001c00,0xe0001,0xc0003800,0x7003c07,0x8380e0f0,0x1e1e03c3,0xc078780f,
      0xf01e007,0x803e0783,0x80e0701c,0xe0381c0,0x7003f007,0x80f00fc0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x6,0x1800061c,0xc0de01,0xc0000000,0xc0000e00,0x70,0xf0000,0x3c00,0x38001c0f,0xe003c,0x3c0,0xe0000e,0x701e00e,
      0x3c0780,0x1e003c0,0x780000,0xfc00001f,0x80000000,0x60e1e780,0x78700f07,0x4380f0,0x38000700,0xf00e38,0x3801c00,0xc0781c07,
      0x81c000e0,0x38e07e0,0xe03c1c00,0x380f0e0,0x1e0003c0,0xe00780f,0xee00f0,0x780e0780,0x1c007800,0xe00001,0xc000e000,0x0,0x700,
      0xf0780e07,0x8041c078,0x38020038,0xe03c1c,0x7001c00,0x1c00707,0x801c0070,0xe1c701c0,0xe0381e03,0x8380f00e,0x80380,0x1c003c1e,
      0x7e00f8,0xf80f1e00,0x3f003c00,0x70000e,0x1c000,0x0,0xf0100f7,0x80078000,0x700078f0,0x10,0x7ff000,0x61208,0x1e0700,0x383800f,
      0x78078000,0x3de007bc,0x0,0x0,0x0,0x0,0x0,0x401c,0x70e0f,0xf7803,0xc0000700,0x1c,0x38c001,0xc000efb8,0x1c00038,0x1e000,0x3c1e0,
      0xc1,0x80000000,0x783c0,0x0,0x0,0x0,0x3c1e,0x18c000,0x0,0x0,0xc180003,0x60000300,0xd80e010,0x3c03c780,0x78f00f1e,0x1e3c03c,
      0x70039c0e,0x70041c,0x38000,0x70000e00,0xe0001,0xc0003800,0x700380f,0x703f070,0x1e0e03c1,0xc078380f,0x701e0e0,0x381c0787,
      0x80f0f01e,0x1e03c3c0,0x7801c007,0xe00e,0x38078700,0xf0e01e1c,0x3c38078,0x700f1c1c,0x78041c,0x1038020,0x70040e00,0x800e0001,
      0xc0003800,0x7001c07,0x380e070,0x1c0e0381,0xc070380e,0x701c007,0x801e0703,0xc1e0783c,0xf0781e0,0xf003f007,0x80e00fc0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c,0xe,0x1801867c,0xc0cf83,0xe0000000,0xe0000e00,
      0x70,0xf0000,0x3c00,0x38000f1e,0xe0070,0x180780,0xe0603e,0x783c01e,0x1e0f01,0x7c003c0,0x780000,0x3c00001e,0x700,0x307fe700,
      0x38701e07,0xc1c383e0,0x38000700,0x7c1e38,0x3801c00,0xe0f01c03,0x81c000e0,0x38e03e0,0x78781c00,0x1e1e0e0,0xe180780,0xe003c1e,
      0x7c00f0,0x781e03c0,0x1c007000,0xe00001,0xc000e000,0x0,0x783,0xf07c1e07,0xc0c1e0f8,0x3e0e0038,0xf07c1c,0x7001c00,0x1c00703,
      0xc01e0070,0xe1c701c0,0xf0781f07,0x83c1f00e,0xe0f80,0x1e003c3e,0x7e00f8,0xf80e0e00,0x3f003800,0x70000e,0x1c000,0x0,0x7830077,
      0xf0000,0x700078f0,0x10,0x20000,0x41208,0xc03c0380,0x3c38007,0x70070000,0x1dc003b8,0x0,0x0,0x0,0x0,0x0,0x707c,0x6070f,0x86077003,
      0x80000700,0x1c,0x3ec401,0xc001c01c,0x1c00038,0xf000,0x1ffc0,0x40,0x80000000,0x3ff80,0x0,0x0,0x0,0x3e3e,0x18c000,0x0,0x0,
      0x8100006,0x60000300,0x1980f070,0x3801c700,0x38e0071c,0xe3801c,0x70039c0e,0x7c1c1c,0x38000,0x70000e00,0xe0001,0xc0003800,
      0x700383e,0x701f03c,0x3c078780,0xf0f01e1e,0x3c3c1c0,0x1c3f0f03,0xc1e0783c,0xf0781e0,0xf001c007,0xe81e,0x3c1f8783,0xf0f07e1e,
      0xfc3c1f8,0x783f1e3e,0x187c0c1f,0x703e0e0,0x7c1c0f83,0x800e0001,0xc0003800,0x7001e0f,0x380e078,0x3c0f0781,0xe0f03c1e,0x783c007,
      0x801e0f03,0xc3e0787c,0xf0f81e1,0xf003f007,0xc1e00fc0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x1c,0xe,0x3801fff8,0x6187ff,0xe0000000,0xe0000e00,0x70,0xf0000,0x3c00,0x38000ffe,0x1fff0ff,0xfe1fff80,0xe07ffc,0x3ffc01c,
      0x1fff01,0xff8003c0,0x780000,0x4000010,0x700,0x301e6700,0x387ffe03,0xffc3ffc0,0x3fff0700,0x3ffe38,0x383ffe0,0xfff01c03,0xc1fff8e0,
      0x38e03e0,0x7ff81c00,0x1ffe0e0,0xf1fff80,0xe003ffe,0x7c00f0,0x781c01c0,0x1c00ffff,0xe00001,0xc000e000,0x0,0x3ff,0x707ffc03,
      0xffc0fff8,0x1ffe0038,0x7ffc1c,0x707fff0,0x1c00701,0xc00ff070,0xe1c701c0,0x7ff01fff,0x1fff00e,0xfff00,0xff81fee,0x7e00f0,
      0x781e0f00,0x1e007ffc,0x70000e,0x1c000,0x0,0x3ff003e,0xf0000,0xe00070e0,0x60830010,0x20000,0x41208,0xfffc01c0,0x1fffe03,0xe00ffff0,
      0xf8001f0,0x0,0x0,0x0,0x0,0x0,0x7ff8,0xc07fd,0xfe03e007,0xffc00700,0x1c,0x1ffc1f,0xffc08008,0x1c00038,0x7000,0x7f00,0x0,0x0,
      0xfe00,0x0,0xffff800,0x0,0x3ff7,0x8018c000,0x0,0x0,0x6,0x60000700,0x19807ff0,0x3801c700,0x38e0071c,0xe3801c,0x70039c0f,0xf03ffc1f,
      0xff83fff0,0x7ffe0fff,0xc1fff03f,0xfe07ffc0,0xfff83ffc,0x701f03f,0xfc07ff80,0xfff01ffe,0x3ffc080,0x83fff03,0xffe07ffc,0xfff81ff,
      0xf001c007,0xeffc,0x1ffb83ff,0x707fee0f,0xfdc1ffb8,0x3ff70ff7,0xf83ffc0f,0xff01ffe0,0x3ffc07ff,0x83fff87f,0xff0fffe1,0xfffc0ffe,
      0x380e03f,0xf807ff00,0xffe01ffc,0x3ff8007,0x803ffe01,0xfee03fdc,0x7fb80ff,0x7001e007,0xffc00780,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c,0xc,0x3801fff0,0x7f83fe,0x70000000,0xe0000e00,0x0,0xf0000,0x3c00,0x700007fc,
      0x1fff0ff,0xfe1ffe00,0xe07ff8,0x1ff801c,0xffe01,0xff0003c0,0x780000,0x0,0x700,0x38000f00,0x3c7ffc01,0xff83ff80,0x3fff0700,
      0x1ffc38,0x383ffe0,0x7fe01c01,0xe1fff8e0,0x38e03e0,0x3ff01c00,0xffc0e0,0x71fff00,0xe001ffc,0x7c00f0,0x783c01e0,0x1c00ffff,
      0xe00000,0xe000e000,0x0,0x1ff,0x7077f801,0xff807fb8,0xffc0038,0x3fdc1c,0x707fff0,0x1c00701,0xe007f070,0xe1c701c0,0x3fe01dfe,
      0xff700e,0x7fe00,0xff80fee,0x3c0070,0x703c0780,0x1e007ffc,0x70000e,0x1c000,0x0,0x1fe001c,0xe0000,0xe000e1c0,0x71c78010,0x20000,
      0x21318,0xfff800c0,0xfffe01,0xc00ffff0,0x70000e0,0x0,0x0,0x0,0x0,0x0,0x3ff0,0x1803fd,0xfe01c007,0xffc00700,0x1c,0xffc1f,0xffc00000,
      0x1c00038,0x7000,0x0,0x0,0x0,0x0,0x0,0xffff800,0x0,0x3ff7,0x8018c000,0x0,0x0,0xc,0x60000e00,0x31803fe0,0x7801ef00,0x3de007bc,
      0xf7801e,0xf003fc0f,0xf01ff81f,0xff83fff0,0x7ffe0fff,0xc1fff03f,0xfe07ffc0,0xfff83ff8,0x701f01f,0xf803ff00,0x7fe00ffc,0x1ff8000,
      0x67fe01,0xffc03ff8,0x7ff00ff,0xe001c007,0xeff8,0xffb81ff,0x703fee07,0xfdc0ffb8,0x1ff70ff7,0xf81ff807,0xfe00ffc0,0x1ff803ff,
      0x3fff87f,0xff0fffe1,0xfffc07fc,0x380e01f,0xf003fe00,0x7fc00ff8,0x1ff0000,0x37fc00,0xfee01fdc,0x3fb807f,0x7001e007,0x7f800780,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c,0xc,0x30007fc0,0x1e00f8,0x78000000,0x70001c00,
      0x0,0xe0000,0x3c00,0x700001f0,0x1fff0ff,0xfe07f800,0xe01fe0,0x7e0038,0x3f800,0xfc0003c0,0x700000,0x0,0x700,0x18000e00,0x1c7ff000,
      0x7e03fe00,0x3fff0700,0x7f038,0x383ffe0,0x1f801c00,0xf1fff8e0,0x38e01e0,0xfc01c00,0x3f80e0,0x787fc00,0xe0007f0,0x7c00f0,0x387800f0,
      0x1c00ffff,0xe00000,0xe000e000,0x0,0xfc,0x7071f000,0x3f003e38,0x3f00038,0x1f1c1c,0x707fff0,0x1c00700,0xf003f070,0xe1c701c0,
      0x1f801c7c,0x7c700e,0x1f800,0x3f8078e,0x3c0070,0x707803c0,0x1c007ffc,0x70000e,0x1c000,0x0,0x7c0008,0x1e0000,0xe000e1c0,0x71c30010,
      0x20000,0x1e1f0,0x3fe00020,0x3ffe00,0x800ffff0,0x2000040,0x0,0x0,0x0,0x0,0x0,0xfc0,0x3001f0,0x78008007,0xffc00700,0x1c,0x3f81f,
      0xffc00000,0x1c00038,0x407000,0x0,0x0,0x0,0x0,0x0,0xffff800,0x0,0x39c7,0x18c000,0x0,0x0,0x18,0x60001c00,0x61801f80,0x7000ee00,
      0x1dc003b8,0x77000e,0xe001f80f,0xf007e01f,0xff83fff0,0x7ffe0fff,0xc1fff03f,0xfe07ffc0,0xfff83fc0,0x700f007,0xe000fc00,0x1f8003f0,
      0x7e0000,0xe1f800,0x7f000fe0,0x1fc003f,0x8001c007,0xe7f0,0x7e380fc,0x701f8e03,0xf1c07e38,0xfc703c1,0xe003f001,0xf8003f00,
      0x7e000fc,0x3fff87f,0xff0fffe1,0xfffc03f8,0x380e00f,0xc001f800,0x3f0007e0,0xfc0000,0x61f800,0x78e00f1c,0x1e3803c,0x7001c007,
      0x1f000700,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x600,0x0,0x0,0x70001c00,0x0,
      0x1c0000,0x0,0xe0000000,0x0,0x0,0x0,0x0,0x0,0x0,0xe00000,0x0,0x0,0xc000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c000,0x0,
      0x0,0x0,0x0,0x0,0xe00000,0x7000e000,0x0,0x0,0x0,0x0,0x0,0x1c00,0x0,0x1c00000,0x0,0x0,0x1c00,0x7000,0x0,0x0,0x0,0x0,0x1c000000,
      0x70000e,0x1c000,0x0,0x0,0x1c0000,0xe000c180,0x10,0x20000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xc000,
      0x0,0x38,0x70e000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3800,0x18c000,0x2000,0x0,0x1f,0xf8003800,0x7fe00000,0x0,0x0,0x0,0x0,0x4000,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x400000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x4000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x400000,
      0x0,0x0,0x1c007,0x700,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x600,0x0,0x0,0x30001800,
      0x0,0x1c0000,0x0,0xe0000000,0x0,0x0,0x0,0x0,0x0,0x0,0xe00000,0x0,0x0,0xe000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1e000,
      0x0,0x0,0x0,0x0,0x0,0xe00000,0x7000e000,0x0,0x0,0x0,0x0,0x0,0x1c00,0x0,0x1c00000,0x0,0x0,0x1c00,0x7000,0x0,0x0,0x0,0x0,0x1c000000,
      0x70000e,0x1c000,0x0,0x0,0x1c0001,0xe001c380,0x10,0x20000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xc000,
      0x0,0x38,0x7fe000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3800,0x18c000,0x3000,0x0,0x1f,0xf8007000,0x7fe00000,0x0,0x0,0x0,0x0,0x6000,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x6000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x1c007,0x700,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x600,0x0,0x0,0x38003800,
      0x0,0x380000,0x1,0xc0000000,0x0,0x0,0x0,0x0,0x0,0x0,0x1c00000,0x0,0x0,0x3c18000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xf000,
      0x0,0x0,0x0,0x0,0x0,0xfe0000,0x380fe000,0x0,0x0,0x0,0x0,0x0,0x3800,0x0,0x1c00000,0x0,0x0,0x1c00,0x7000,0x0,0x0,0x0,0x0,0x38000000,
      0x78000e,0x3c000,0x0,0x0,0x180001,0xc0018300,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xc000,0x0,
      0x38,0x1f8000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3800,0x18c000,0x1800,0x0,0x0,0x6000e000,0x1800000,0x0,0x0,0x0,0x0,0x3000,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x38007,0xe00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x600,0x0,0x0,0x18003000,
      0x0,0x300000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1800000,0x0,0x0,0x1ff8000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x4000,0x0,0x0,
      0x0,0x0,0x0,0xfe0000,0xfe000,0x0,0x0,0x0,0x0,0x0,0x607800,0x0,0x3c00000,0x0,0x0,0x1c00,0x7000,0x0,0x0,0x0,0x0,0x78000000,
      0x3f800e,0x3f8000,0x0,0x0,0x300043,0xc0018200,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xc000,
      0x0,0x38,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3800,0x0,0x11800,0x0,0x0,0x6001ff00,0x1800000,0x0,0x0,0x0,0x0,0x23000,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x23000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x78007,
      0x1e00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x600,0x0,0x0,0x1c007000,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7f8000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xfe0000,
      0xfe000,0x0,0x0,0x0,0x0,0x0,0x7ff000,0x0,0x7f800000,0x0,0x0,0x1c00,0x7000,0x0,0x0,0x0,0x3,0xf8000000,0x3f800e,0x3f8000,0x0,
      0x0,0x10007f,0x80000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xc000,0x0,0x38,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x3800,0x0,0x1f800,0x0,0x0,0x6001ff00,0x1800000,0x0,0x0,0x0,0x0,0x3f000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3f000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3f8007,0xfe00,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7fff8,0x0,0x0,0x0,0x0,0x7fe000,0x0,
      0x7f000000,0x0,0x0,0x1c00,0x7000,0x0,0x0,0x0,0x3,0xf0000000,0xf800e,0x3e0000,0x0,0x0,0x7f,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3800,0x0,0x1f000,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x3e000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3e000,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x3f0007,0xfc00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x7fff8,0x0,0x0,0x0,0x0,0x1fc000,0x0,0x7e000000,0x0,0x0,0x1c00,0x7000,0x0,0x0,0x0,0x3,0xc0000000,0xe,0x0,
      0x0,0x0,0x3e,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x3800,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c0007,0xf000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7fff8,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0xe,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0 };

    // Definition of a 29x57 font.
    const unsigned int font29x57[29*57*256/32] = {
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x781e00,0x0,0x0,0x7,0x81e00000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7c0000,0xf8000,0x7e00000,0x0,0x7,
      0xc0000000,0x0,0x7c00,0xf80,0x7e000,0x0,0x7c00000,0xf80000,0x7e000000,0x0,0x0,0x1f00,0x3e0,0x1f800,0x0,0x0,0x0,0x3,0xe0000000,
      0x7c00003f,0x0,0xf8,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x3c3c00,0x0,0x0,0x3,0xc3c00000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3e1f00,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3e0000,
      0x1f0000,0x7e00000,0xf838001f,0xf80001f,0xf0000000,0x0,0x3e00,0x1f00,0x7e000,0x3e1f000,0x3e00000,0x1f00000,0x7e00003e,0x1f000000,
      0x3e0,0xe0000f80,0x7c0,0x1f800,0x3e0e00,0x7c3e000,0x0,0x1,0xf0000000,0xf800003f,0x1f0f,0x800001f0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1e7800,0x0,0x0,
      0x1,0xe7800000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3e1f00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1e0000,0x1e0000,0xff00001,0xfe38001f,0xf80003f,
      0xf8000000,0x0,0x1e00,0x1e00,0xff000,0x3e1f000,0x1e00000,0x1e00000,0xff00003e,0x1f000000,0x7f8,0xe0000780,0x780,0x3fc00,0x7f8e00,
      0x7c3e000,0x0,0x0,0xf0000000,0xf000007f,0x80001f0f,0x800001e0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xef000,0x0,0x0,0x0,0xef000000,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3e1f00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xf0000,0x3c0000,0x1e780003,0xfff8001f,0xf80003c,0x78000000,0x0,0xf00,0x3c00,0x1e7800,
      0x3e1f000,0xf00000,0x3c00001,0xe780003e,0x1f000000,0xfff,0xe00003c0,0xf00,0x79e00,0xfffe00,0x7c3e000,0x0,0x0,0x78000001,0xe00000f3,
      0xc0001f0f,0x800003c0,0x0,0x0,0x0,0x0,0x0,0x0,0x7,0xc0000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7e000,0x0,0x0,0x0,0x7e000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x3e1f00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x78000,0x780000,0x3c3c0003,0x8ff0001f,0xf800078,0x3c000000,0x0,0x780,0x7800,0x3c3c00,0x3e1f000,0x780000,0x7800003,0xc3c0003e,
      0x1f000000,0xe3f,0xc00001e0,0x1e00,0xf0f00,0xe3fc00,0x7c3e000,0x0,0x0,0x3c000003,0xc00001e1,0xe0001f0f,0x80000780,0x0,0x0,
      0x0,0x0,0x0,0x0,0x1f,0xf0000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x7e000,0x0,0x0,0x0,0x7e000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3e1f00,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xfc00,0x7e000,0xfe000,0x0,0x3c000,0xf00000,0x781e0003,
      0x83e0001f,0xf800070,0x1c000000,0x0,0x3c0,0xf000,0x781e00,0x3e1f000,0x3c0000,0xf000007,0x81e0003e,0x1f000000,0xe0f,0x800000f0,
      0x3c00,0x1e0780,0xe0f800,0x7c3e000,0x0,0x0,0x1e000007,0x800003c0,0xf0001f0f,0x80000f00,0x0,0x0,0x0,0x0,0x0,0x0,0x3f,0xf8000000,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3fc00,0x1fe000,0x3ff800,0x0,0x0,0x0,0x0,0x0,0x70,0x1c000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c,0x78000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1f00000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xf80,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x78,0xf000000,0x0,0x0,0x780f0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7c0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x3fc00,0x1fe000,0x3ffc00,0x0,0x0,0x0,0x0,0x0,0x70,0x1c000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1f00000,0x3e000,0x3e00000,0x0,0x78,0x3c000000,0x0,0x1f000,0x3e0,
      0x3e000,0x0,0x1f000000,0x3e0000,0x3e000000,0x0,0x0,0x7c00,0xf8,0xf800,0x0,0x0,0x0,0xf,0x80000000,0x1f00001f,0x0,0x3e,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x30000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xf80000,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0xf80,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x781c0000,0x38,0xe000000,0x0,0x0,0x380e0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xf80,0x0,0x0,0x0,0x0,0x0,0x0,0x39c00,0x1ce000,0x303e00,
      0x0,0x0,0x0,0x0,0x0,0x78,0x3c000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x4000,0x0,0x0,0x0,0x0,
      0x0,0x0,0xf80000,0x7c000,0x3e00000,0xf0380000,0x70,0x1c000000,0x0,0xf800,0x7c0,0x3e000,0x0,0xf800000,0x7c0000,0x3e000000,
      0x0,0x3c0,0xe0003e00,0x1f0,0xf800,0x3c0e00,0x0,0x0,0x7,0xc0000000,0x3e00001f,0x0,0x7c,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x30000000,0xff,0x0,
      0xf8,0xf8000,0x1c000,0x0,0x0,0x0,0x0,0x1f,0xc0000000,0x1ff8,0xff00,0x0,0x0,0x3fe000,0x0,0x1fc00001,0xfe000000,0x0,0x0,0x0,
      0x0,0x7f800,0x0,0x0,0x0,0xff00000,0x0,0x0,0xff,0x0,0x0,0x0,0x0,0x0,0x0,0x3,0xf8000000,0xfe,0x0,0x7f80,0x0,0x0,0x0,0x0,0x0,
      0x0,0x3f,0xf0000000,0x7fe0,0x0,0x0,0x780000,0x1,0xe0000000,0x0,0x780000,0x3,0xfe000000,0x78000,0x3c00,0xf000,0x7800003,0xffe00000,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xfc0000f0,0x3f000,0x0,0x0,0x3fc00,0x0,0x0,0x1fc000,0x0,0x0,0x0,0x1fc0,
      0x0,0xff000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xfe1c0000,0x1c,0x1c000000,0x0,0x0,0x1c1c0,0x0,0x0,0x0,0x0,0x1fe0000,
      0x0,0x0,0x1ff,0x1f0f8,0x0,0xff000,0x0,0x0,0x0,0x3f,0xff00000f,0x80000000,0xfe0,0x3f80,0xf00,0x0,0x0,0x0,0x1,0xf8000003,0xe0000000,
      0x1c00,0xe000,0xe00,0x0,0x0,0x0,0x0,0x0,0x3c,0x78000000,0xff,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7f0,0x3f80,0x1fc00,0xfe000,
      0x7f0000,0x0,0x1fc07000,0x0,0x0,0x0,0x0,0x0,0x3f800,0x780000,0x78000,0x7f00001,0xfc38001f,0xf800070,0x1c000000,0x0,0x7800,
      0x780,0x7f000,0x3e1f000,0x7800000,0x780000,0x7f00003e,0x1f0003f0,0x7f0,0xe0001e00,0x1e0,0x1fc00,0x7f0e00,0x7c3e000,0x0,0x3,
      0xc0000000,0x3c00003f,0x80001f0f,0x80000078,0x1e0000,0x3e1f00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x3c1e0000,0x1e078000,0x30000000,0x3ff,0xc00001e0,0xf0,
      0x78000,0x1c000,0x0,0x0,0x0,0x0,0x1e0007f,0xf000007e,0x1ffff,0x7ffe0,0x1f80,0x3ffff80,0xfff803,0xfffff800,0xfff80007,0xff800000,
      0x0,0x0,0x0,0x0,0x1ffe00,0x0,0xfe0003,0xfff80000,0x3ffe01ff,0xe00003ff,0xffe01fff,0xff0003ff,0xe01e0007,0x803ffff0,0xfff80,
      0x3c000fc0,0x7800001f,0x8003f07e,0x1e000f,0xfe0007ff,0xf00003ff,0x8007ffe0,0x1fff8,0x7fffffe,0xf0003c1,0xe000079e,0xf1f,0x1f3e0,
      0x1f01ff,0xfff8003f,0xf003c000,0x7fe0,0x3f00,0x0,0x3c0000,0x1,0xe0000000,0x0,0x780000,0xf,0xfe000000,0x78000,0x3c00,0xf000,
      0x7800003,0xffe00000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7,0xfc0000f0,0x3fe00,0x0,0x0,0xfff00,0x0,0x0,0x3fe000,
      0x0,0x0,0x0,0x1dc0,0x0,0x3fff00,0x0,0x3ffff80,0x1f,0xffff8000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xff1c07ff,0x3c0f001e,0x3c000000,
      0x0,0x0,0x1e3c0,0xf80007c,0x0,0x780000,0x0,0xfff8000,0x3e00,0x1f00000,0x7ff,0xc001f0f8,0x0,0x3ffc00,0x0,0x0,0x0,0x3f,0xff00003f,
      0xe0000000,0x3ff8,0xffe0,0x1e00,0x0,0xfffc00,0x0,0x7,0xf800000f,0xf8000000,0x1c00,0xe000,0xe00,0xf000,0x1fc000,0xfe0000,0x7f00000,
      0x3f800001,0xfc00003f,0xf80000ff,0xffc003ff,0xe007ffff,0xc03ffffe,0x1fffff0,0xfffff80,0x7fffe003,0xffff001f,0xfff800ff,0xffc01ffc,
      0xfc00,0x3c001ffc,0xffe0,0x7ff00,0x3ff800,0x1ffc000,0x0,0x7ff8f0f0,0x3c0780,0x1e03c00,0xf01e000,0x783e0001,0xf01e0000,0xffe00,
      0x3c0000,0xf0000,0x7700001,0xfe38001f,0xf800070,0x1c000000,0x0,0x3c00,0xf00,0x77000,0x3e1f000,0x3c00000,0xf00000,0x7700003e,
      0x1f0000f8,0xc0007f8,0xe0000f00,0x3c0,0x1dc00,0x7f8e00,0x7c3e000,0x0,0x1,0xe0000000,0x7800003b,0x80001f0f,0x800000f0,0x1e0000,
      0x3e1f00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x780000,0x3c1e0000,0x1e070000,0x300001f0,0x7ff,0xc00001e0,0x1e0,0x7c000,0x1c000,0x0,0x0,0x0,0x0,0x3c000ff,0xf80007fe,
      0x3ffff,0x801ffff8,0x1f80,0x3ffff80,0x3fff803,0xfffff801,0xfffc000f,0xffc00000,0x0,0x0,0x0,0x0,0x7fff80,0x0,0xfe0003,0xffff0000,
      0xffff01ff,0xfc0003ff,0xffe01fff,0xff000fff,0xf01e0007,0x803ffff0,0xfff80,0x3c001f80,0x7800001f,0xc007f07e,0x1e001f,0xff0007ff,
      0xfc0007ff,0xc007fffc,0x3fffc,0x7fffffe,0xf0003c1,0xf0000f9e,0xf0f,0x8003e1e0,0x1e01ff,0xfff8003f,0xf001e000,0x7fe0,0x3f00,
      0x0,0x1e0000,0x1,0xe0000000,0x0,0x780000,0x1f,0xfe000000,0x78000,0x3c00,0xf000,0x7800003,0xffe00000,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0xf,0xfc0000f0,0x3ff00,0x0,0x0,0x1fff80,0x0,0x0,0xffe000,0x0,0x0,0x0,0x3de0,0x0,0x7fff80,0x0,0xfffff80,
      0x1f,0xffff8000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1,0xe7bc07ff,0x3e1f000f,0x78000000,0x0,0x0,0xf780,0x7800078,0x0,0x780000,0x180000,
      0x1fff8000,0x1e00,0x1e0003c,0xfff,0xc001f0f8,0x0,0x7ffe00,0x0,0x0,0x0,0x3f,0xff00007f,0xf0000000,0x3ffc,0xfff0,0x3c00,0x0,
      0x7fffc00,0x0,0x7,0xf800003f,0xfe000000,0x1c00,0xe000,0xe00,0xf000,0x1fc000,0xfe0000,0x7f00000,0x3f800001,0xfc00001f,0xe00001ff,
      0xffc00fff,0xf007ffff,0xc03ffffe,0x1fffff0,0xfffff80,0x7fffe003,0xffff001f,0xfff800ff,0xffc01fff,0xc000fc00,0x3c003ffe,0x1fff0,
      0xfff80,0x7ffc00,0x3ffe000,0x0,0xfffce0f0,0x3c0780,0x1e03c00,0xf01e000,0x781e0001,0xe01e0000,0x3fff00,0x1e0000,0x1e0000,0xf780003,
      0xcf78001f,0xf800078,0x3c000000,0x0,0x1e00,0x1e00,0xf7800,0x3e1f000,0x1e00000,0x1e00000,0xf780003e,0x1f0000fc,0x7c000f3d,
      0xe0000780,0x780,0x3de00,0xf3de00,0x7c3e000,0x0,0x0,0xf0000000,0xf000007b,0xc0001f0f,0x800001e0,0x1e0000,0x3e1f00,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,
      0x3c1e0000,0x1e0f0000,0x300007fc,0xfff,0xc00001e0,0x1e0,0x3c000,0x1c000,0x0,0x0,0x0,0x0,0x3c001ff,0xfc001ffe,0x3ffff,0xc01ffffc,
      0x3f80,0x3ffff80,0x7fff803,0xfffff803,0xfffe001f,0xffe00000,0x0,0x0,0x0,0x0,0xffff80,0x7f800,0xfe0003,0xffff8001,0xffff01ff,
      0xff0003ff,0xffe01fff,0xff001fff,0xf01e0007,0x803ffff0,0xfff80,0x3c003f00,0x7800001f,0xc007f07f,0x1e003f,0xff8007ff,0xff000fff,
      0xe007ffff,0x7fffc,0x7fffffe,0xf0003c0,0xf0000f1e,0xf07,0x8003c1f0,0x3e01ff,0xfff8003f,0xf001e000,0x7fe0,0x7f80,0x0,0xe0000,
      0x1,0xe0000000,0x0,0x780000,0x1f,0xfe000000,0x78000,0x3c00,0xf000,0x7800003,0xffe00000,0x0,0x0,0x0,0x0,0x0,0x0,0x3c000,0x0,
      0x0,0x0,0x0,0x0,0xf,0xfc0000f0,0x3ff00,0x0,0x0,0x3fff80,0x0,0x0,0xffe000,0x0,0x0,0x0,0x78f0,0x0,0xffff80,0x0,0x3fffff80,0x1f,
      0xffff8000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1,0xc7f80070,0x3e1f0007,0x70000000,0x0,0x0,0x7700,0x7c000f8,0x0,0x780000,0x180000,
      0x3fff8000,0x1f00,0x3e0003c,0x1f03,0xc001f0f8,0x0,0x703f00,0x0,0x0,0x0,0x3f,0xff0000f0,0xf8000000,0x303e,0xc0f8,0x7800,0x0,
      0xffffc00,0x0,0x7,0x3800003e,0x3e000000,0x1c00,0xe000,0x3c00,0xf000,0x1fc000,0xfe0000,0x7f00000,0x3f800001,0xfc00000f,0xe00001ff,
      0xffc01fff,0xf007ffff,0xc03ffffe,0x1fffff0,0xfffff80,0x7fffe003,0xffff001f,0xfff800ff,0xffc01fff,0xf000fe00,0x3c007fff,0x3fff8,
      0x1fffc0,0xfffe00,0x7fff000,0x1,0xffffc0f0,0x3c0780,0x1e03c00,0xf01e000,0x781f0003,0xe01e0000,0x3fff80,0xe0000,0x3c0000,0x1e3c0003,
      0x8ff0001f,0xf80003c,0x78000000,0x0,0xe00,0x3c00,0x1e3c00,0x3e1f000,0xe00000,0x3c00001,0xe3c0003e,0x1f00007f,0xf8000e3f,0xc0000380,
      0xf00,0x78f00,0xe3fc00,0x7c3e000,0x0,0x0,0x70000001,0xe00000f1,0xe0001f0f,0x800003c0,0x1e0000,0x3e1f00,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x3c1e0000,0x3c0f0000,
      0x30000ffe,0xf80,0xc00001e0,0x3c0,0x1e000,0x101c040,0x0,0x0,0x0,0x0,0x78003f0,0x7e001ffe,0x3f807,0xe01f00fe,0x3f80,0x3ffff80,
      0x7e01803,0xfffff007,0xe03f003f,0x3f00000,0x0,0x0,0x0,0x0,0xfc0fc0,0x3ffe00,0xfe0003,0xffffc003,0xf81f01ff,0xff8003ff,0xffe01fff,
      0xff003f01,0xf01e0007,0x803ffff0,0xfff80,0x3c007e00,0x7800001f,0xc007f07f,0x1e007e,0xfc007ff,0xff801f83,0xf007ffff,0x800fc07c,
      0x7fffffe,0xf0003c0,0xf0000f0f,0x1e07,0xc007c0f8,0x7c01ff,0xfff8003c,0xf000,0x1e0,0xffc0,0x0,0xf0000,0x1,0xe0000000,0x0,0x780000,
      0x3e,0x0,0x78000,0x3c00,0xf000,0x7800000,0x1e00000,0x0,0x0,0x0,0x0,0x0,0x0,0x3c000,0x0,0x0,0x0,0x0,0x0,0x1f,0x800000f0,0x1f80,
      0x0,0x0,0x7e0780,0x0,0x0,0x1f82000,0x0,0x0,0x0,0x7070,0x0,0x1f80f80,0x0,0x7fffff80,0x1f,0xffff8000,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x1,0xc3f80070,0x3f3f0007,0xf0000000,0x0,0x0,0x7f00,0x3e001f0,0x0,0x780000,0x180000,0x7f018000,0xf80,0x7c0003c,0x3e00,
      0x4001f0f8,0xfe00,0x400f00,0x0,0x0,0x0,0x7f000000,0xe0,0x38000000,0x1e,0x38,0x7800,0x0,0x1ffe1c00,0x0,0x0,0x38000078,0xf000000,
      0x1c00,0xe000,0x7f800,0xf000,0x1fc000,0xfe0000,0x7f00000,0x3f800001,0xfc00001f,0xf00001ff,0xffc03f81,0xf007ffff,0xc03ffffe,
      0x1fffff0,0xfffff80,0x7fffe003,0xffff001f,0xfff800ff,0xffc01fff,0xf800fe00,0x3c00fc1f,0x8007e0fc,0x3f07e0,0x1f83f00,0xfc1f800,
      0x3,0xf07fc0f0,0x3c0780,0x1e03c00,0xf01e000,0x780f8007,0xc01e0000,0x7e0fc0,0xf0000,0x3c0000,0x1c1c0003,0x87f0001f,0xf80003f,
      0xf8000000,0x0,0xf00,0x3c00,0x1c1c00,0x3e1f000,0xf00000,0x3c00001,0xc1c0003e,0x1f00003f,0xc0000e1f,0xc00003c0,0xf00,0x70700,
      0xe1fc00,0x7c3e000,0x0,0x0,0x78000001,0xe00000e0,0xe0001f0f,0x800003c0,0x1e0000,0x3e1f00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x3c1e0000,0x3c0f0001,0xff801e0f,
      0x1f00,0x1e0,0x3c0,0x1e000,0x3c1c1e0,0x0,0x0,0x0,0x0,0x78007c0,0x1f001f9e,0x3c001,0xf010003e,0x7780,0x3c00000,0xf800000,0xf007,
      0xc01f007c,0x1f80000,0x0,0x0,0x0,0x0,0xe003e0,0x7fff00,0x1ef0003,0xc007e007,0xc00301e0,0x1fc003c0,0x1e00,0x7c00,0x301e0007,
      0x80007800,0x780,0x3c00fc00,0x7800001f,0xe00ff07f,0x1e00f8,0x3e00780,0x1fc03e00,0xf807801f,0xc01f001c,0xf000,0xf0003c0,0xf0000f0f,
      0x1e03,0xc00f8078,0x780000,0xf0003c,0xf000,0x1e0,0x1f3e0,0x0,0x78000,0x1,0xe0000000,0x0,0x780000,0x3c,0x0,0x78000,0x0,0x0,
      0x7800000,0x1e00000,0x0,0x0,0x0,0x0,0x0,0x0,0x3c000,0x0,0x0,0x0,0x0,0x0,0x1f,0xf0,0xf80,0x0,0x0,0xf80180,0x0,0x0,0x1e00000,
      0x0,0x0,0x0,0xe038,0x0,0x3e00380,0x0,0xfe0f0000,0x0,0xf0000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1,0xc0f00070,0x3b370003,0xe0000000,
      0x0,0x0,0x3e00,0x1e001e0,0x0,0x780000,0x180000,0x7c000000,0x780,0x780003c,0x3c00,0x0,0x7ffc0,0x780,0x0,0x0,0x3,0xffe00000,
      0x1c0,0x3c000000,0xe,0x38,0xf000,0x0,0x3ffe1c00,0x0,0x0,0x38000078,0xf000000,0x1c00,0xe000,0x7f000,0xf000,0x3de000,0x1ef0000,
      0xf780000,0x7bc00003,0xde00001e,0xf00003e7,0x80007c00,0x30078000,0x3c0000,0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001,
      0xe0001e03,0xfc00fe00,0x3c01f007,0xc00f803e,0x7c01f0,0x3e00f80,0x1f007c00,0x7,0xc01f80f0,0x3c0780,0x1e03c00,0xf01e000,0x78078007,
      0x801e0000,0x7803c0,0x78000,0x780000,0x380e0003,0x81e00000,0x1f,0xf0000000,0x0,0x780,0x7800,0x380e00,0x0,0x780000,0x7800003,
      0x80e00000,0x1ff,0x80000e07,0x800001e0,0x1e00,0xe0380,0xe07800,0x0,0x0,0x0,0x3c000003,0xc00001c0,0x70000000,0x780,0x1e0000,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x780000,0x3c1e0000,0x3c0e0007,0xfff01c07,0x1e00,0x1e0,0x780,0xf000,0x3e1c3e0,0x0,0x0,0x0,0x0,0xf0007c0,0x1f00181e,0x20000,
      0xf000001f,0xf780,0x3c00000,0x1f000000,0x1f00f,0x800f8078,0xf80000,0x0,0x0,0x0,0x0,0x8003e0,0x1fc0f80,0x1ef0003,0xc001e007,
      0x800101e0,0x7e003c0,0x1e00,0x7800,0x101e0007,0x80007800,0x780,0x3c00f800,0x7800001e,0xe00ef07f,0x801e00f0,0x1e00780,0x7c03c00,
      0x78078007,0xc01e0004,0xf000,0xf0003c0,0x78001e0f,0x1e03,0xe00f807c,0xf80000,0x1f0003c,0x7800,0x1e0,0x3e1f0,0x0,0x3c000,0x1,
      0xe0000000,0x0,0x780000,0x3c,0x0,0x78000,0x0,0x0,0x7800000,0x1e00000,0x0,0x0,0x0,0x0,0x0,0x0,0x3c000,0x0,0x0,0x0,0x0,0x0,
      0x1e,0xf0,0x780,0x0,0x0,0x1f00080,0x0,0x0,0x3c00000,0x0,0x0,0x0,0x1e03c,0x0,0x3c00080,0x0,0xf80f0000,0x0,0x1f0000,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x70,0x3bf70003,0xe0000000,0x0,0x0,0x3e00,0x1f003e0,0x0,0x780000,0x180000,0x78000000,0x7c0,0xf80003c,
      0x3c00,0x0,0x1f01f0,0x780,0x0,0x0,0xf,0x80f80000,0x1c0,0x1c000000,0xe,0x38,0x1e000,0x0,0x7ffe1c00,0x0,0x0,0x380000f0,0x7800000,
      0x1c00,0xe000,0x7fc00,0xf000,0x3de000,0x1ef0000,0xf780000,0x7bc00003,0xde00001e,0xf00003c7,0x80007800,0x10078000,0x3c0000,
      0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001,0xe0001e00,0x7e00ff00,0x3c01e003,0xc00f001e,0x7800f0,0x3c00780,0x1e003c00,
      0x7,0x800f00f0,0x3c0780,0x1e03c00,0xf01e000,0x7807c00f,0x801e0000,0xf803c0,0x3c000,0xf00000,0x780f0000,0x0,0x7,0xc0000000,
      0x0,0x3c0,0xf000,0x780f00,0x0,0x3c0000,0xf000007,0x80f00000,0x7ff,0xc0000000,0xf0,0x3c00,0x1e03c0,0x0,0x0,0x0,0x0,0x1e000007,
      0x800003c0,0x78000000,0xf00,0x1e0000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x3c1e0000,0x3c1e001f,0xfff03803,0x80001e00,0x1e0,0x780,0xf000,0xf9cf80,
      0x0,0x0,0x0,0x0,0xf000780,0xf00001e,0x0,0xf800000f,0xe780,0x3c00000,0x1e000000,0x1e00f,0x78078,0x7c0000,0x0,0x0,0x0,0x0,0x1e0,
      0x3f003c0,0x1ef0003,0xc000f00f,0x800001e0,0x1f003c0,0x1e00,0xf000,0x1e0007,0x80007800,0x780,0x3c01f000,0x7800001e,0xe00ef07f,
      0x801e01f0,0x1e00780,0x3c07c00,0x78078003,0xc03e0000,0xf000,0xf0003c0,0x78001e0f,0x1e01,0xf01f003c,0xf00000,0x3e0003c,0x7800,
      0x1e0,0x7c0f8,0x0,0x0,0x1,0xe0000000,0x0,0x780000,0x3c,0x0,0x78000,0x0,0x0,0x7800000,0x1e00000,0x0,0x0,0x0,0x0,0x0,0x0,0x3c000,
      0x0,0x0,0x0,0x0,0x0,0x1e,0xf0,0x780,0x0,0x0,0x1e00000,0x0,0x0,0x3c00000,0x0,0x8,0x40,0x0,0x7e0000,0x7c00000,0x1,0xf00f0000,
      0x0,0x3e0000,0x0,0x3f,0xfc0,0xfc3f0,0xfc3f0,0x0,0x0,0x0,0x70,0x39e70000,0x0,0x0,0x0,0x0,0xf003c0,0x0,0x0,0x180000,0xf8000000,
      0x3c0,0xf00003c,0x3c00,0x0,0x3c0078,0x7ff80,0x0,0x0,0x1e,0x3c0000,0x1c0,0x1c000000,0xe,0xf0,0x0,0x0,0x7ffe1c00,0x0,0x0,0x380000f0,
      0x7800000,0x1c00,0xe000,0x3c00,0x0,0x3de000,0x1ef0000,0xf780000,0x7bc00003,0xde00001e,0xf00003c7,0x8000f800,0x78000,0x3c0000,
      0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001,0xe0001e00,0x1f00ff00,0x3c03e003,0xc01f001e,0xf800f0,0x7c00780,0x3e003c00,
      0xf,0x800f80f0,0x3c0780,0x1e03c00,0xf01e000,0x7803c00f,0x1fffc0,0xf001e0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x307,0xe0000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1e0000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x3c1e0000,0x781e003f,0xfff03803,
      0x80001e00,0x1e0,0xf80,0xf000,0x3dde00,0x0,0x0,0x0,0x0,0xf000f00,0x780001e,0x0,0x7800000f,0x1e780,0x3c00000,0x3e000000,0x3e00f,
      0x780f0,0x7c0000,0x0,0x0,0x0,0x0,0x1e0,0x7c001e0,0x3ef8003,0xc000f00f,0x1e0,0xf003c0,0x1e00,0xf000,0x1e0007,0x80007800,0x780,
      0x3c03e000,0x7800001e,0xf01ef07b,0xc01e01e0,0xf00780,0x3e07800,0x3c078003,0xe03c0000,0xf000,0xf0003c0,0x78001e0f,0x1e00,0xf01e003e,
      0x1f00000,0x3c0003c,0x7800,0x1e0,0x78078,0x0,0x0,0x1,0xe0000000,0x0,0x780000,0x3c,0x0,0x78000,0x0,0x0,0x7800000,0x1e00000,
      0x0,0x0,0x0,0x0,0x0,0x0,0x3c000,0x0,0x0,0x0,0x0,0x0,0x1e,0xf0,0x780,0x0,0x0,0x1e00000,0x0,0x0,0x3c00000,0x0,0x18,0xc0,0x0,
      0xe70000,0x7800000,0x1,0xe00f0000,0x0,0x3c0000,0x0,0x3f,0xfc0,0xfc1f0,0x1f83f0,0x0,0x0,0x0,0x70,0x39e70000,0x0,0x0,0x0,0x0,
      0xf807c0,0x0,0x0,0x180000,0xf0000000,0x3e0,0x1f00003c,0x3e00,0x0,0x70001c,0x3fff80,0x0,0x0,0x38,0xe0000,0x1c0,0x1c000078,
      0x1c,0x1fe0,0x0,0x0,0xfffe1c00,0x0,0x0,0x380000f0,0x7800000,0x1c00,0xe000,0xe00,0x0,0x7df000,0x3ef8000,0x1f7c0000,0xfbe00007,
      0xdf00003c,0x780003c7,0x8000f000,0x78000,0x3c0000,0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001,0xe0001e00,0xf00f780,
      0x3c03c001,0xe01e000f,0xf00078,0x78003c0,0x3c001e00,0xf,0xf80f0,0x3c0780,0x1e03c00,0xf01e000,0x7803e01f,0x1ffff8,0xf001e0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3,0xe0000000,0x0,0x0,0x0,0x0,0x0,0x0,0xc000,0x0,0x0,0x0,0x0,0x1e0000,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x780000,0x3c1e0000,0x781e003e,0x30703803,0x80001e00,0x1e0,0xf00,0x7800,0xff800,0x1e0000,0x0,0x0,0x0,0x1e000f00,0x780001e,
      0x0,0x7800000f,0x3c780,0x3c00000,0x3c000000,0x3c00f,0x780f0,0x3c0000,0x0,0x0,0x2000000,0x800000,0x1e0,0x78000e0,0x3c78003,
      0xc000f01e,0x1e0,0xf803c0,0x1e00,0x1e000,0x1e0007,0x80007800,0x780,0x3c07c000,0x7800001e,0x701cf07b,0xc01e01e0,0xf00780,0x1e07800,
      0x3c078001,0xe03c0000,0xf000,0xf0003c0,0x7c003e0f,0x1e00,0xf83e001e,0x1e00000,0x7c0003c,0x3c00,0x1e0,0xf807c,0x0,0x0,0x1fe0001,
      0xe1fc0000,0x7f00003,0xf8780007,0xf000003c,0x7f0,0x783f0,0x0,0x0,0x7800000,0x1e00000,0x3e0f8000,0xfc00007,0xf8000007,0xf00001fc,
      0xf,0xc0003fc0,0x3c000,0x0,0x0,0x0,0x0,0x0,0x1e,0xf0,0x780,0x0,0x0,0x3c00000,0x0,0x0,0x3c00000,0x0,0x18,0xc0,0x0,0x1818000,
      0x7800000,0x1,0xe00f0000,0x0,0x7c0000,0x0,0x1f,0x80001f80,0x7c1f8,0x1f83e0,0x0,0x0,0x0,0x70,0x38c70007,0xf8000000,0x7f03,
      0xf0000000,0x0,0x780780,0x0,0x0,0xfe0000,0xf0000000,0x1e0,0x1e00003c,0x3f00,0x0,0xe07f0e,0x7fff80,0x0,0x0,0x70,0x70000,0x1c0,
      0x1c000078,0x3c,0x1fc0,0x0,0x0,0xfffe1c00,0x0,0x0,0x380000f0,0x7800000,0x1c00,0xe000,0xe00,0x0,0x78f000,0x3c78000,0x1e3c0000,
      0xf1e00007,0x8f00003c,0x78000787,0x8001e000,0x78000,0x3c0000,0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001,0xe0001e00,
      0xf80f780,0x3c03c001,0xe01e000f,0xf00078,0x78003c0,0x3c001e00,0xf,0x1f80f0,0x3c0780,0x1e03c00,0xf01e000,0x7801e01e,0x1ffffc,
      0xf007e0,0x3fc000,0x1fe0000,0xff00000,0x7f800003,0xfc00001f,0xe0000fc0,0xfc00007f,0xfe0,0x7f00,0x3f800,0x1fc000,0x0,0x0,0x0,
      0x1,0xf000001f,0x80000ff0,0x7f80,0x3fc00,0x1fe000,0xff0000,0x1f80000,0x1fc1e000,0x0,0x0,0x0,0x0,0x1e1fc0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x3c1e0000,
      0x781c007c,0x30003803,0x80001f00,0x1e0,0xf00,0x7800,0x7f000,0x1e0000,0x0,0x0,0x0,0x1e000f00,0x780001e,0x0,0x7800000f,0x3c780,
      0x3c00000,0x3c000000,0x3c00f,0x780f0,0x3c0000,0x0,0x0,0x1e000000,0xf00000,0x3e0,0xf0000e0,0x3c78003,0xc000f01e,0x1e0,0x7803c0,
      0x1e00,0x1e000,0x1e0007,0x80007800,0x780,0x3c0f8000,0x7800001e,0x701cf079,0xe01e01e0,0xf00780,0x1e07800,0x3c078001,0xe03c0000,
      0xf000,0xf0003c0,0x3c003c0f,0x3e00,0x787c001f,0x3e00000,0xf80003c,0x3c00,0x1e0,0x1f003e,0x0,0x0,0x1fffc001,0xe7ff0000,0x3ffe000f,
      0xfe78003f,0xfc001fff,0xfe001ffc,0xf0078ffc,0x1ffc00,0x7ff000,0x7800f80,0x1e0000f,0x7f1fc01e,0x3ff0001f,0xfe00079f,0xfc0007ff,
      0x3c003c7f,0xf001fff8,0x1fffff0,0x3c003c0,0xf0000f1e,0xf1f,0x7c1f0,0x1f00ff,0xffe0001e,0xf0,0x780,0x0,0x0,0x3c00000,0x100000,
      0x0,0x7800000,0x0,0x18,0xc0,0x0,0x1818000,0x7800000,0x1,0xe00f0000,0x1000000,0xf80000,0x40000002,0xf,0x80001f00,0x7e0f8,0x1f07c0,
      0x0,0x0,0x0,0x70,0x38c7003f,0xff000000,0xff8f,0xf8000100,0xffffe,0x7c0f80,0x0,0x0,0x3ffc000,0xf0000020,0x1001f0,0x3c00003c,
      0x1f80,0x0,0x1c3ffc7,0x7c0780,0x0,0x0,0xe3,0xff038000,0xe0,0x38000078,0x78,0x1ff0,0x0,0x3c003c0,0xfffe1c00,0x0,0x0,0x380000f0,
      0x7800000,0x1c00,0xe000,0xe00,0xf000,0x78f000,0x3c78000,0x1e3c0000,0xf1e00007,0x8f00003c,0x78000787,0x8001e000,0x78000,0x3c0000,
      0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001,0xe0001e00,0x780f3c0,0x3c03c001,0xe01e000f,0xf00078,0x78003c0,0x3c001e00,
      0x4000200f,0x3f80f0,0x3c0780,0x1e03c00,0xf01e000,0x7801f03e,0x1ffffe,0xf01fe0,0x3fff800,0x1fffc000,0xfffe0007,0xfff0003f,
      0xff8001ff,0xfc003ff3,0xfe0003ff,0xe0007ff8,0x3ffc0,0x1ffe00,0xfff000,0x3ff80001,0xffc0000f,0xfe00007f,0xf000003f,0xf8003c7f,
      0xe0003ffc,0x1ffe0,0xfff00,0x7ff800,0x3ffc000,0x1f80000,0xfff1c03c,0x3c01e0,0x1e00f00,0xf007800,0x781f0001,0xf01e7ff0,0x7c0007c,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,
      0x3c1e003f,0xfffff078,0x30003803,0x80000f00,0x1e0,0x1f00,0x7800,0x7f000,0x1e0000,0x0,0x0,0x0,0x3c000f00,0x780001e,0x0,0x7800000f,
      0x78780,0x3c00000,0x3c000000,0x7c00f,0x780f0,0x3c0007,0xe000003f,0x0,0xfe000000,0xfe0000,0x3c0,0x1f000070,0x7c7c003,0xc000f01e,
      0x1e0,0x7803c0,0x1e00,0x1e000,0x1e0007,0x80007800,0x780,0x3c1f0000,0x7800001e,0x783cf079,0xe01e03c0,0xf00780,0x1e0f000,0x3c078001,
      0xe03c0000,0xf000,0xf0003c0,0x3c003c07,0x81f03c00,0x7c7c000f,0x87c00000,0xf00003c,0x1e00,0x1e0,0x3e001f,0x0,0x0,0x3fffe001,
      0xefff8000,0x7fff001f,0xff78007f,0xfe001fff,0xfe003ffe,0xf0079ffe,0x1ffc00,0x7ff000,0x7801f00,0x1e0000f,0xffbfe01e,0x7ff8003f,
      0xff0007bf,0xfe000fff,0xbc003cff,0xf803fffc,0x1fffff0,0x3c003c0,0x78001e1e,0xf0f,0x800f80f0,0x1e00ff,0xffe0001e,0xf0,0x780,
      0x0,0x0,0x3c00000,0x380000,0x0,0x7800000,0x0,0x18,0xc0,0x0,0x1008000,0x7800000,0x3,0xe00f0000,0x3800000,0xf00000,0xe0000007,
      0xf,0x80001f00,0x3e0f8,0x1e07c0,0x0,0x0,0x0,0x70,0x3807007f,0xff800000,0x1ffdf,0xfc000380,0xffffe,0x3e1f00,0x0,0x0,0xfffe000,
      0xf0000030,0x3800f8,0x7c00003c,0xfc0,0x0,0x18780c3,0xf00780,0x80100,0x0,0xc3,0xffc18000,0xf0,0x78000078,0xf0,0xf0,0x0,0x3c003c0,
      0xfffe1c00,0x0,0x0,0x380000f0,0x7800801,0x1c00,0xe000,0x1e00,0xf000,0xf8f800,0x7c7c000,0x3e3e0001,0xf1f0000f,0x8f80007c,0x7c000787,
      0x8001e000,0x78000,0x3c0000,0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001,0xe0001e00,0x780f3c0,0x3c078001,0xe03c000f,
      0x1e00078,0xf0003c0,0x78001e00,0xe000701f,0x3fc0f0,0x3c0780,0x1e03c00,0xf01e000,0x7800f87c,0x1e007f,0xf07e00,0x7fffc00,0x3fffe001,
      0xffff000f,0xfff8007f,0xffc003ff,0xfe007ff7,0xff0007ff,0xf000fffc,0x7ffe0,0x3fff00,0x1fff800,0x3ff80001,0xffc0000f,0xfe00007f,
      0xf00000ff,0xf8003cff,0xf0007ffe,0x3fff0,0x1fff80,0xfffc00,0x7ffe000,0x1f80001,0xfffb803c,0x3c01e0,0x1e00f00,0xf007800,0x780f0001,
      0xe01efff8,0x3c00078,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x780000,0x3c1e003f,0xfffff078,0x30001c07,0xf80,0x1e0,0x1e00,0x3c00,0xff800,0x1e0000,0x0,0x0,0x0,0x3c001e00,
      0x3c0001e,0x0,0x7800001e,0x70780,0x3c00000,0x78000000,0x78007,0x800f00f0,0x3e0007,0xe000003f,0x3,0xfe000000,0xff8000,0x7c0,
      0x1e000070,0x783c003,0xc001f01e,0x1e0,0x7803c0,0x1e00,0x1e000,0x1e0007,0x80007800,0x780,0x3c3e0000,0x7800001e,0x3838f079,
      0xe01e03c0,0x780780,0x1e0f000,0x1e078001,0xe03c0000,0xf000,0xf0003c0,0x3c007c07,0x81f03c00,0x3ef80007,0x87800000,0x1f00003c,
      0x1e00,0x1e0,0x7c000f,0x80000000,0x0,0x3ffff001,0xffffc000,0xffff003f,0xff7800ff,0xff001fff,0xfe007ffe,0xf007bffe,0x1ffc00,
      0x7ff000,0x7803e00,0x1e0000f,0xffffe01e,0xfff8007f,0xff8007ff,0xff001fff,0xbc003dff,0xf807fffc,0x1fffff0,0x3c003c0,0x78001e0f,
      0x1e07,0xc01f00f0,0x1e00ff,0xffe0001e,0xf0,0x780,0x0,0x0,0x7c00000,0x7c0000,0x0,0x7800000,0x0,0x18,0xc0,0x0,0x1018000,0x7800000,
      0x3,0xc00f0000,0x7c00000,0x1f00001,0xf000000f,0x80000007,0xc0003e00,0x1e07c,0x3e0780,0x0,0x0,0x0,0x70,0x380700ff,0xff800000,
      0x3ffff,0xfe0007c0,0xffffe,0x1e1e00,0x0,0x780000,0x1fffe000,0xf0000078,0x7c0078,0x7800003c,0xff0,0x0,0x38e0003,0x80f00780,
      0x180300,0x0,0x1c3,0x81e1c000,0x7f,0xf0000078,0x1e0,0x38,0x0,0x3c003c0,0xfffe1c00,0x0,0x0,0x380000f0,0x7800c01,0x80001c00,
      0xe000,0x603e00,0xf000,0xf07800,0x783c000,0x3c1e0001,0xe0f0000f,0x7800078,0x3c000f87,0x8001e000,0x78000,0x3c0000,0x1e00000,
      0xf000000,0xf00000,0x7800000,0x3c000001,0xe0001e00,0x780f3c0,0x3c078000,0xf03c0007,0x81e0003c,0xf0001e0,0x78000f01,0xf000f81e,
      0x7bc0f0,0x3c0780,0x1e03c00,0xf01e000,0x78007878,0x1e001f,0xf0f800,0x7fffe00,0x3ffff001,0xffff800f,0xfffc007f,0xffe003ff,
      0xff007fff,0xff800fff,0xf001fffe,0xffff0,0x7fff80,0x3fffc00,0x3ff80001,0xffc0000f,0xfe00007f,0xf00001ff,0xfc003dff,0xf000ffff,
      0x7fff8,0x3fffc0,0x1fffe00,0xffff000,0x1f80003,0xffff803c,0x3c01e0,0x1e00f00,0xf007800,0x780f0001,0xe01ffffc,0x3c00078,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,
      0x3c1e003f,0xfffff078,0x30001e0f,0x300780,0x1e0,0x1e00,0x3c00,0x3dde00,0x1e0000,0x0,0x0,0x0,0x78001e00,0x3c0001e,0x0,0xf800003e,
      0xf0780,0x3dfc000,0x783f8000,0xf8007,0xc01f00f0,0x3e0007,0xe000003f,0x1f,0xfc000000,0x7ff000,0xf80,0x3e007c70,0x783c003,0xc001e03c,
      0x1e0,0x3c03c0,0x1e00,0x3c000,0x1e0007,0x80007800,0x780,0x3c7c0000,0x7800001e,0x3878f078,0xf01e03c0,0x780780,0x1e0f000,0x1e078001,
      0xe03e0000,0xf000,0xf0003c0,0x1e007807,0x83f03c00,0x3ef00007,0xcf800000,0x3e00003c,0xf00,0x1e0,0xf80007,0xc0000000,0x0,0x3e01f801,
      0xfe07e001,0xf80f007e,0x7f801f8,0x1f801fff,0xfe00fc0f,0xf007f83f,0x1ffc00,0x7ff000,0x7807c00,0x1e0000f,0x87e1e01f,0xe0fc00fc,
      0xfc007f8,0x1f803f03,0xfc003df0,0x3807e03c,0x1fffff0,0x3c003c0,0x78003e0f,0x1e03,0xe03e00f8,0x3e00ff,0xffe0001e,0xf0,0x780,
      0x0,0x0,0x7800000,0xfe0000,0x0,0x7800000,0x0,0x18,0xc0,0x0,0x1818000,0x7c00000,0x3,0xc00f0000,0xfe00000,0x3e00003,0xf800001f,
      0xc0000007,0xc0003e00,0x1e03c,0x3c0f80,0x0,0x0,0x0,0x70,0x380700fc,0x7800000,0x7c1fe,0x3e000fe0,0xffffe,0x1f3e00,0x0,0x780000,
      0x3f98e000,0xf000003c,0xfcf8007c,0xf800003c,0x3ffc,0x0,0x31c0001,0x80f00f80,0x380700,0x0,0x183,0x80e0c000,0x3f,0xe0000078,
      0x3c0,0x38,0x0,0x3c003c0,0xfffe1c00,0x0,0x0,0x38000078,0xf000e01,0xc003ffe0,0x1fff00,0x7ffc00,0xf000,0xf07800,0x783c000,0x3c1e0001,
      0xe0f0000f,0x7800078,0x3c000f07,0x8003c000,0x78000,0x3c0000,0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001,0xe0001e00,
      0x3c0f1e0,0x3c078000,0xf03c0007,0x81e0003c,0xf0001e0,0x78000f00,0xf801f01e,0xf3c0f0,0x3c0780,0x1e03c00,0xf01e000,0x78007cf8,
      0x1e000f,0x80f0f000,0x7c03f00,0x3e01f801,0xf00fc00f,0x807e007c,0x3f003e0,0x1f80707f,0x8f801f80,0xf003f03f,0x1f81f8,0xfc0fc0,
      0x7e07e00,0x3ff80001,0xffc0000f,0xfe00007f,0xf00003ff,0xfc003fc1,0xf801f81f,0x800fc0fc,0x7e07e0,0x3f03f00,0x1f81f800,0x1f80007,
      0xe07f003c,0x3c01e0,0x1e00f00,0xf007800,0x780f8003,0xe01fe07e,0x3e000f8,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x3f,0xfffff078,0x30000ffe,0x1f007c0,0x0,0x1e00,
      0x3c00,0xf9cf80,0x1e0000,0x0,0x0,0x0,0x78001e00,0x3c0001e,0x0,0xf00000fc,0x1e0780,0x3fff800,0x78ffe000,0xf0003,0xe03e00f0,
      0x3e0007,0xe000003f,0x7f,0xe01fffff,0xf00ffc00,0x1f80,0x3c01ff70,0x783c003,0xc007e03c,0x1e0,0x3c03c0,0x1e00,0x3c000,0x1e0007,
      0x80007800,0x780,0x3cfc0000,0x7800001e,0x3c78f078,0xf01e03c0,0x780780,0x3e0f000,0x1e078003,0xc01f0000,0xf000,0xf0003c0,0x1e007807,
      0x83f83c00,0x1ff00003,0xcf000000,0x3e00003c,0xf00,0x1e0,0x0,0x0,0x0,0x20007801,0xfc03e003,0xe003007c,0x3f803e0,0x7c0003c,
      0xf807,0xf007e00f,0x3c00,0xf000,0x780f800,0x1e0000f,0x87e1f01f,0x803c00f8,0x7c007f0,0xf803e01,0xfc003f80,0x80f8004,0x3c000,
      0x3c003c0,0x3c003c0f,0x1e03,0xe03e0078,0x3c0000,0x7c0001e,0xf0,0x780,0x0,0x0,0x3ffff800,0x1ff0000,0x0,0x7800000,0x0,0x18,
      0xc0,0x0,0x1818000,0x3e00000,0x3,0xc00f0000,0x1ff00000,0x3e00007,0xfc00003f,0xe0000003,0xc0003c00,0xf03c,0x3c0f00,0x0,0x0,
      0x0,0x70,0x380701f0,0x800000,0x780fc,0x1e001ff0,0x7c,0xf3c00,0x0,0x780000,0x7e182000,0xf000001f,0xfff00ffc,0xffc0003c,0x3cfe,
      0x0,0x31c0001,0x80f01f80,0x780f00,0x0,0x183,0x80e0c000,0xf,0x80000078,0x780,0x38,0x0,0x3c003c0,0x7ffe1c00,0x0,0x0,0x38000078,
      0xf000f01,0xe003ffe0,0x1fff00,0x7ff800,0xf000,0xf07800,0x783c000,0x3c1e0001,0xe0f0000f,0x78000f8,0x3e000f07,0x8003c000,0x78000,
      0x3c0000,0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001,0xe0001e00,0x3c0f1e0,0x3c078000,0xf03c0007,0x81e0003c,0xf0001e0,
      0x78000f00,0x7c03e01e,0x1e3c0f0,0x3c0780,0x1e03c00,0xf01e000,0x78003cf0,0x1e0007,0x80f1e000,0x4000f00,0x20007801,0x3c008,
      0x1e0040,0xf00200,0x780403f,0x7803e00,0x3007c00f,0x803e007c,0x1f003e0,0xf801f00,0x780000,0x3c00000,0x1e000000,0xf00007f0,
      0x3e003f00,0x7801f00f,0x800f807c,0x7c03e0,0x3e01f00,0x1f00f800,0x1f80007,0xc03e003c,0x3c01e0,0x1e00f00,0xf007800,0x78078003,
      0xc01fc03e,0x1e000f0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x780000,0x0,0xf078007c,0x300007fc,0x7e00fe0,0x0,0x1e00,0x3c00,0x3e1c3e0,0x1e0000,0x0,0x0,0x0,0xf0001e00,
      0x3c0001e,0x1,0xf000fff8,0x1e0780,0x3fffe00,0x79fff000,0x1f0001,0xfffc00f0,0x7e0007,0xe000003f,0x3ff,0x801fffff,0xf003ff80,
      0x3f00,0x3c03fff0,0xf01e003,0xffffc03c,0x1e0,0x3c03ff,0xffc01fff,0xfe03c000,0x1fffff,0x80007800,0x780,0x3df80000,0x7800001e,
      0x1c70f078,0x781e03c0,0x780780,0x3c0f000,0x1e078007,0xc01f8000,0xf000,0xf0003c0,0x1e007807,0x83f83c00,0xfe00003,0xff000000,
      0x7c00003c,0x780,0x1e0,0x0,0x0,0x0,0x7c01,0xf801f007,0xc00100f8,0x1f803c0,0x3c0003c,0x1f003,0xf007c00f,0x80003c00,0xf000,
      0x783f000,0x1e0000f,0x3c0f01f,0x3e01f0,0x3e007e0,0x7c07c00,0xfc003f00,0xf0000,0x3c000,0x3c003c0,0x3c003c0f,0x1e01,0xf07c007c,
      0x7c0000,0xfc0001e,0xf0,0x780,0x0,0x0,0x3ffff000,0x3838000,0x0,0x7800000,0x0,0x18,0xc0,0x0,0xff0000,0x3f00000,0x3,0xc00fff00,
      0x38380000,0x7c0000e,0xe000070,0x70000001,0xe0003c00,0xf01e,0x780e00,0x0,0x0,0x0,0x0,0x1e0,0x0,0x780f8,0xf003838,0xfc,0xffc00,
      0x0,0x780000,0x7c180000,0xf000000f,0xffe00fff,0xffc0003c,0x783f,0x80000000,0x6380000,0xc0f83f80,0xf81f00,0x0,0x303,0x80e06000,
      0x0,0x78,0xf00,0x78,0x0,0x3c003c0,0x7ffe1c00,0x0,0x0,0x3800003c,0x3e000f81,0xf003ffe0,0x1fff00,0x1fc000,0xf000,0x1e03c00,
      0xf01e000,0x780f0003,0xc078001e,0x3c000f0,0x1e000f07,0xff83c000,0x7ffff,0x803ffffc,0x1ffffe0,0xfffff00,0xf00000,0x7800000,
      0x3c000001,0xe0001e00,0x3c0f0f0,0x3c078000,0xf03c0007,0x81e0003c,0xf0001e0,0x78000f00,0x3e07c01e,0x1e3c0f0,0x3c0780,0x1e03c00,
      0xf01e000,0x78003ff0,0x1e0007,0x80f1e000,0xf80,0x7c00,0x3e000,0x1f0000,0xf80000,0x7c0001e,0x3c07c00,0x10078007,0x803c003c,
      0x1e001e0,0xf000f00,0x780000,0x3c00000,0x1e000000,0xf00007c0,0x1e003e00,0x7c03e007,0xc01f003e,0xf801f0,0x7c00f80,0x3e007c00,
      0xf,0x801f003c,0x3c01e0,0x1e00f00,0xf007800,0x7807c007,0xc01f801f,0x1f001f0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x0,0xe078003c,0x300001f0,0x3f801ff0,0x0,
      0x3c00,0x1e00,0x3c1c1e0,0x1e0000,0x0,0x0,0x0,0xf0001e0f,0x3c0001e,0x3,0xe000fff0,0x3c0780,0x3ffff00,0x7bfff800,0x1e0000,0x7ff00078,
      0x7e0007,0xe000003f,0x1ffc,0x1fffff,0xf0007ff0,0x7e00,0x3c07c3f0,0xf01e003,0xffff003c,0x1e0,0x3c03ff,0xffc01fff,0xfe03c000,
      0x1fffff,0x80007800,0x780,0x3ffc0000,0x7800001e,0x1ef0f078,0x781e03c0,0x780780,0x7c0f000,0x1e07801f,0x800ff000,0xf000,0xf0003c0,
      0xf00f807,0x83b83c00,0xfc00001,0xfe000000,0xf800003c,0x780,0x1e0,0x0,0x0,0x0,0x3c01,0xf000f007,0xc00000f0,0xf80780,0x3c0003c,
      0x1e001,0xf007c007,0x80003c00,0xf000,0x787e000,0x1e0000f,0x3c0f01f,0x1e01e0,0x1e007c0,0x3c07800,0x7c003f00,0xf0000,0x3c000,
      0x3c003c0,0x3e007c07,0x80003c00,0xf8f8003c,0x780000,0xf80001e,0xf0,0x780,0x0,0x0,0x7ffff000,0x601c000,0x3,0xffff0000,0x0,
      0xfff,0xf8007fff,0xc0000000,0x7e003c,0x1fe0000,0xc0003,0xc00fff00,0x601c0000,0xf800018,0x70000c0,0x38000001,0xe0007800,0x701e,
      0x701e00,0x0,0x0,0x0,0x0,0x1e0,0x6,0x700f8,0xf00601c,0xf8,0x7f800,0x0,0x780000,0xf8180000,0xf000000f,0x87c00fff,0xffc0003c,
      0xf01f,0xc0000000,0x6380000,0xc07ff780,0x1f03e03,0xfffffe00,0x303,0x81c06000,0x0,0x1ffff,0xfe001e00,0x180f8,0x0,0x3c003c0,
      0x3ffe1c00,0x3f00000,0x0,0x3800003f,0xfe0007c0,0xf8000000,0x18000000,0xc0000006,0x1f000,0x1e03c00,0xf01e000,0x780f0003,0xc078001e,
      0x3c000f0,0x1e001f07,0xff83c000,0x7ffff,0x803ffffc,0x1ffffe0,0xfffff00,0xf00000,0x7800000,0x3c000001,0xe000fff8,0x3c0f0f0,
      0x3c078000,0xf03c0007,0x81e0003c,0xf0001e0,0x78000f00,0x1f0f801e,0x3c3c0f0,0x3c0780,0x1e03c00,0xf01e000,0x78001fe0,0x1e0007,
      0x80f1e000,0x780,0x3c00,0x1e000,0xf0000,0x780000,0x3c0001e,0x3c07c00,0xf0007,0x8078003c,0x3c001e0,0x1e000f00,0x780000,0x3c00000,
      0x1e000000,0xf0000f80,0x1f003e00,0x3c03c003,0xc01e001e,0xf000f0,0x7800780,0x3c003c00,0xf,0x3f003c,0x3c01e0,0x1e00f00,0xf007800,
      0x7803c007,0x801f000f,0xf001e0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x1,0xe078003f,0xb0000000,0xfc003cf0,0x0,0x3c00,0x1e00,0x101c040,0x1e0000,0x0,0x0,0x1,
      0xe0001e1f,0x83c0001e,0x7,0xe000fff0,0x3c0780,0x3c03f80,0x7fc0fc00,0x1e0000,0xfff80078,0xfe0007,0xe000003f,0x7fe0,0x1fffff,
      0xf0000ffc,0xfc00,0x780f81f0,0xf01e003,0xffff003c,0x1e0,0x3c03ff,0xffc01fff,0xfe03c000,0x1fffff,0x80007800,0x780,0x3ffc0000,
      0x7800001e,0x1ef0f078,0x3c1e03c0,0x780780,0x1fc0f000,0x1e07ffff,0x7ff00,0xf000,0xf0003c0,0xf00f007,0xc3b87c00,0x7c00001,0xfe000000,
      0xf800003c,0x3c0,0x1e0,0x0,0x0,0x0,0x3c01,0xf000f007,0x800000f0,0xf80780,0x1e0003c,0x1e001,0xf0078007,0x80003c00,0xf000,0x78fc000,
      0x1e0000f,0x3c0f01e,0x1e01e0,0x1e007c0,0x3c07800,0x7c003e00,0xf0000,0x3c000,0x3c003c0,0x1e007807,0x80003c00,0x7df0003c,0x780000,
      0x1f00001e,0xf0,0x780,0x0,0x0,0x7800000,0xe7ce000,0x3,0xffff0000,0x0,0xfff,0xf8007fff,0xc0000000,0x1f0,0xffe000,0x1c0003,
      0xc00fff00,0xe7ce0000,0xf800039,0xf38001cf,0x9c000000,0xe0007800,0x780e,0x701c00,0x0,0x0,0x0,0x0,0x1e0,0x7,0xf0078,0xf00e7ce,
      0x1f0,0x7f800,0x0,0x780000,0xf0180000,0xf000000e,0x1c0001f,0xe000003c,0xf007,0xe0000000,0x6380000,0xc03fe780,0x3e07c03,0xfffffe00,
      0x303,0xffc06000,0x0,0x1ffff,0xfe003ffe,0x1fff0,0x0,0x3c003c0,0x1ffe1c00,0x3f00000,0x7,0xffc0001f,0xfc0003e0,0x7c000001,0xfc00000f,
      0xe000007f,0x1e000,0x1e03c00,0xf01e000,0x780f0003,0xc078001e,0x3c000f0,0x1e001e07,0xff83c000,0x7ffff,0x803ffffc,0x1ffffe0,
      0xfffff00,0xf00000,0x7800000,0x3c000001,0xe000fff8,0x3c0f078,0x3c078000,0xf03c0007,0x81e0003c,0xf0001e0,0x78000f00,0xf9f001e,
      0x783c0f0,0x3c0780,0x1e03c00,0xf01e000,0x78001fe0,0x1e0007,0x80f1e000,0x780,0x3c00,0x1e000,0xf0000,0x780000,0x3c0001e,0x3c07800,
      0xf0003,0xc078001e,0x3c000f0,0x1e000780,0x780000,0x3c00000,0x1e000000,0xf0000f00,0xf003c00,0x3c03c003,0xc01e001e,0xf000f0,
      0x7800780,0x3c003c00,0xf,0x7f003c,0x3c01e0,0x1e00f00,0xf007800,0x7803c007,0x801f000f,0xf001e0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x1,0xe070001f,0xf8000007,
      0xf0007cf8,0x7800000,0x3c00,0x1e00,0x1c000,0x1e0000,0x0,0x0,0x1,0xe0001e1f,0x83c0001e,0xf,0xc000fff8,0x780780,0x2000f80,0x7f803e00,
      0x3e0003,0xfffe007c,0x1fe0000,0x0,0x3ff00,0x0,0x1ff,0x8001f000,0x780f00f0,0x1f00f003,0xffffc03c,0x1e0,0x3c03ff,0xffc01fff,
      0xfe03c00f,0xf81fffff,0x80007800,0x780,0x3ffe0000,0x7800001e,0xee0f078,0x3c1e03c0,0x7807ff,0xff80f000,0x1e07fffe,0x3ffe0,
      0xf000,0xf0003c0,0xf00f003,0xc7bc7800,0xfc00000,0xfc000001,0xf000003c,0x3c0,0x1e0,0x0,0x0,0x0,0x3c01,0xe000f80f,0x800001e0,
      0xf80f00,0x1e0003c,0x3c000,0xf0078007,0x80003c00,0xf000,0x79f8000,0x1e0000f,0x3c0f01e,0x1e03c0,0x1f00780,0x3e0f000,0x7c003e00,
      0xf0000,0x3c000,0x3c003c0,0x1e007807,0x81e03c00,0x7df0003e,0xf80000,0x3e00003e,0xf0,0x7c0,0xfc000,0x80000000,0x7800000,0x1e7cf000,
      0x3,0xffff0000,0x0,0x18,0xc0,0x0,0xf80,0x7ffc00,0x380003,0xc00fff01,0xe7cf0000,0x1f000079,0xf3c003cf,0x9e000000,0xe0007000,
      0x380e,0xe01c00,0x0,0x0,0x0,0x0,0x1e0,0x3,0x800f0078,0xf01e7cf,0x3e0,0x3f000,0x0,0x780000,0xf018001f,0xfff8001e,0x1e0000f,
      0xc000003c,0xf003,0xe0000000,0x6380000,0xc00fc780,0x7c0f803,0xfffffe00,0x303,0xfe006000,0x0,0x1ffff,0xfe003ffe,0x1ffe0,0x0,
      0x3c003c0,0xffe1c00,0x3f00000,0x7,0xffc00007,0xf00001f0,0x3e00001f,0xfc0000ff,0xe00007ff,0x3e000,0x3e01e00,0x1f00f000,0xf8078007,
      0xc03c003e,0x1e001e0,0xf001e07,0xff83c000,0x7ffff,0x803ffffc,0x1ffffe0,0xfffff00,0xf00000,0x7800000,0x3c000001,0xe000fff8,
      0x3c0f078,0x3c078000,0xf03c0007,0x81e0003c,0xf0001e0,0x78000f00,0x7fe001e,0xf03c0f0,0x3c0780,0x1e03c00,0xf01e000,0x78000fc0,
      0x1e0007,0x80f1f000,0x780,0x3c00,0x1e000,0xf0000,0x780000,0x3c0001e,0x3c0f800,0x1e0003,0xc0f0001e,0x78000f0,0x3c000780,0x780000,
      0x3c00000,0x1e000000,0xf0000f00,0xf003c00,0x3c078003,0xe03c001f,0x1e000f8,0xf0007c0,0x78003e00,0x1e,0xf7803c,0x3c01e0,0x1e00f00,
      0xf007800,0x7803e00f,0x801e000f,0x80f803e0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x1,0xe0f0000f,0xff00001f,0x8000f87c,0x7800000,0x3c00,0x1e00,0x1c000,0x7fffff80,
      0x0,0x0,0x3,0xc0001e1f,0x83c0001e,0x1f,0x800000fe,0xf00780,0x7c0,0x7f001e00,0x3c0007,0xe03f003f,0x3fe0000,0x0,0x3fc00,0x0,
      0x7f,0x8001e000,0x781f00f0,0x1e00f003,0xc007e03c,0x1e0,0x3c03c0,0x1e00,0x3c00f,0xf81e0007,0x80007800,0x780,0x3f9f0000,0x7800001e,
      0xfe0f078,0x3c1e03c0,0x7807ff,0xff00f000,0x1e07fff8,0xfff8,0xf000,0xf0003c0,0xf81f003,0xc7bc7800,0xfe00000,0x78000003,0xe000003c,
      0x1e0,0x1e0,0x0,0x0,0x0,0x1fffc01,0xe000780f,0x1e0,0x780f00,0x1e0003c,0x3c000,0xf0078007,0x80003c00,0xf000,0x7bf0000,0x1e0000f,
      0x3c0f01e,0x1e03c0,0xf00780,0x1e0f000,0x3c003c00,0xf8000,0x3c000,0x3c003c0,0x1f00f807,0x81f03c00,0x3fe0001e,0xf00000,0x7c00007c,
      0xf0,0x3e0,0x3ff801,0x80000000,0x7800000,0x3cfcf800,0x3,0xffff0000,0x0,0x18,0xc0,0x0,0x7c00,0x1fff00,0x700003,0xc00f0003,
      0xcfcf8000,0x3e0000f3,0xf3e0079f,0x9f000000,0xf000,0x1000,0x0,0x0,0x0,0x0,0x0,0x1f0,0x1,0xc00f0078,0xf03cfcf,0x800007c0,0x1e000,
      0x0,0x780001,0xe018001f,0xfff8001c,0xe00007,0x8000003c,0xf001,0xf0000000,0x6380000,0xc0000000,0xf81f003,0xfffffe00,0x303,
      0x87006000,0x0,0x1ffff,0xfe003ffe,0x7f00,0x0,0x3c003c0,0x3fe1c00,0x3f00000,0x7,0xffc00000,0xf8,0x1f0001ff,0xf0000fff,0x80007ffc,
      0xfc000,0x3c01e00,0x1e00f000,0xf0078007,0x803c003c,0x1e001e0,0xf001e07,0x8003c000,0x78000,0x3c0000,0x1e00000,0xf000000,0xf00000,
      0x7800000,0x3c000001,0xe000fff8,0x3c0f078,0x3c078000,0xf03c0007,0x81e0003c,0xf0001e0,0x78000f00,0x3fc001e,0x1e03c0f0,0x3c0780,
      0x1e03c00,0xf01e000,0x78000780,0x1e0007,0x80f0fc00,0x3fff80,0x1fffc00,0xfffe000,0x7fff0003,0xfff8001f,0xffc0001e,0x3c0f000,
      0x1e0003,0xc0f0001e,0x78000f0,0x3c000780,0x780000,0x3c00000,0x1e000000,0xf0001e00,0xf803c00,0x3c078001,0xe03c000f,0x1e00078,
      0xf0003c0,0x78001e07,0xfffffe1e,0x1e7803c,0x3c01e0,0x1e00f00,0xf007800,0x7801e00f,0x1e0007,0x807803c0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x3,0xc0f00007,
      0xffc0007e,0xf03e,0x7800000,0x3c00,0x1e00,0x1c000,0x7fffff80,0x0,0x0,0x3,0xc0001e1f,0x83c0001e,0x3f,0x3e,0xf00780,0x3c0,0x7e001e00,
      0x7c000f,0x800f001f,0xffde0000,0x0,0x3e000,0x0,0xf,0x8003e000,0x781e0070,0x1e00f003,0xc001f03c,0x1e0,0x3c03c0,0x1e00,0x3c00f,
      0xf81e0007,0x80007800,0x780,0x3f1f0000,0x7800001e,0x7c0f078,0x1e1e03c0,0x7807ff,0xfc00f000,0x1e07fffe,0xffc,0xf000,0xf0003c0,
      0x781e003,0xc71c7800,0x1ff00000,0x78000003,0xe000003c,0x1e0,0x1e0,0x0,0x0,0x0,0xffffc01,0xe000780f,0x1e0,0x780fff,0xffe0003c,
      0x3c000,0xf0078007,0x80003c00,0xf000,0x7ff0000,0x1e0000f,0x3c0f01e,0x1e03c0,0xf00780,0x1e0f000,0x3c003c00,0x7f000,0x3c000,
      0x3c003c0,0xf00f007,0xc1f07c00,0x1fc0001f,0x1f00000,0xfc000ff8,0xf0,0x1ff,0xfffe07,0x80000000,0x7800000,0x7ffcfc00,0x0,0xf000000,
      0x0,0x18,0xc0,0x0,0x3e000,0x1ff80,0xe00003,0xc00f0007,0xffcfc000,0x3e0001ff,0xf3f00fff,0x9f800000,0x6000,0x0,0x0,0x7c000,
      0x0,0x0,0x0,0xfe,0x0,0xe00f007f,0xff07ffcf,0xc0000fc0,0x1e000,0x0,0x780001,0xe018001f,0xfff8001c,0xe00007,0x80000000,0xf800,
      0xf0000000,0x6380000,0xc0000000,0x1f03c000,0x1e00,0x303,0x83806000,0x0,0x78,0x0,0x0,0x0,0x3c003c0,0xfe1c00,0x3f00000,0x0,
      0x0,0x3c,0xf801fff,0xfff8,0x7ffc0,0x1f8000,0x3c01e00,0x1e00f000,0xf0078007,0x803c003c,0x1e001e0,0xf003c07,0x8003c000,0x78000,
      0x3c0000,0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001,0xe0001e00,0x3c0f03c,0x3c078000,0xf03c0007,0x81e0003c,0xf0001e0,
      0x78000f00,0x1f8001e,0x1e03c0f0,0x3c0780,0x1e03c00,0xf01e000,0x78000780,0x1e000f,0x80f0ff00,0x1ffff80,0xffffc00,0x7fffe003,
      0xffff001f,0xfff800ff,0xffc007ff,0xffc0f000,0x1fffff,0xc0fffffe,0x7fffff0,0x3fffff80,0x780000,0x3c00000,0x1e000000,0xf0001e00,
      0x7803c00,0x3c078001,0xe03c000f,0x1e00078,0xf0003c0,0x78001e07,0xfffffe1e,0x3c7803c,0x3c01e0,0x1e00f00,0xf007800,0x7801f01f,
      0x1e0007,0x807c07c0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x780000,0x3,0xc0f00000,0xfff003f0,0x1f00f03e,0x7800000,0x3c00,0x1e00,0x1c000,0x7fffff80,0x0,0x7ff80000,0x3,
      0xc0001e0f,0x3c0001e,0x7e,0x1f,0x1e00780,0x3e0,0x7e000f00,0x78000f,0x7800f,0xff9e0000,0x0,0x3fc00,0x0,0x7f,0x8003c000,0x781e0070,
      0x3e00f803,0xc000f03c,0x1e0,0x3c03c0,0x1e00,0x3c00f,0xf81e0007,0x80007800,0x780,0x3e0f8000,0x7800001e,0x7c0f078,0x1e1e03c0,
      0x7807ff,0xf000f000,0x1e07807f,0xfe,0xf000,0xf0003c0,0x781e003,0xc71c7800,0x3ef00000,0x78000007,0xc000003c,0x1e0,0x1e0,0x0,
      0x0,0x0,0x1ffffc01,0xe000780f,0x1e0,0x780fff,0xffe0003c,0x3c000,0xf0078007,0x80003c00,0xf000,0x7ff0000,0x1e0000f,0x3c0f01e,
      0x1e03c0,0xf00780,0x1e0f000,0x3c003c00,0x7ff80,0x3c000,0x3c003c0,0xf00f003,0xc1f07800,0x1fc0000f,0x1e00000,0xf8000ff0,0xf0,
      0xff,0xffffff,0x80000000,0x3fffc000,0xfff9fe00,0x0,0xf000000,0x0,0x18,0xc0,0x0,0x1f0000,0x1fc0,0x1c00003,0xc00f000f,0xff9fe000,
      0x7c0003ff,0xe7f81fff,0x3fc00000,0x0,0x0,0x0,0xfe000,0x1ffffc0f,0xfffffc00,0x0,0xff,0xf0000000,0x700f007f,0xff0fff9f,0xe0000f80,
      0x1e000,0x0,0x780001,0xe018001f,0xfff8001c,0xe00fff,0xffc00000,0xf800,0xf0000000,0x6380000,0xc0ffff80,0x3e078000,0x1e00,0x7ff80303,
      0x83c06000,0x0,0x78,0x0,0x0,0x0,0x3c003c0,0xe1c00,0x3f00000,0x0,0x7f,0xff00001e,0x7c1fff0,0xfff80,0x7ffc00,0x3f0000,0x7c01f00,
      0x3e00f801,0xf007c00f,0x803e007c,0x1f003e0,0xf803c07,0x8003c000,0x78000,0x3c0000,0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001,
      0xe0001e00,0x3c0f03c,0x3c078000,0xf03c0007,0x81e0003c,0xf0001e0,0x78000f00,0x1f8001e,0x3c03c0f0,0x3c0780,0x1e03c00,0xf01e000,
      0x78000780,0x1e001f,0xf07f80,0x3ffff80,0x1ffffc00,0xffffe007,0xffff003f,0xfff801ff,0xffc03fff,0xffc0f000,0x1fffff,0xc0fffffe,
      0x7fffff0,0x3fffff80,0x780000,0x3c00000,0x1e000000,0xf0001e00,0x7803c00,0x3c078001,0xe03c000f,0x1e00078,0xf0003c0,0x78001e07,
      0xfffffe1e,0x787803c,0x3c01e0,0x1e00f00,0xf007800,0x7800f01e,0x1e0007,0x803c0780,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x1ff,0xffff8000,0x3ff80fc0,0x7fc1e01f,
      0x7800000,0x3c00,0x1e00,0x0,0x7fffff80,0x0,0x7ff80000,0x7,0x80001e00,0x3c0001e,0xfc,0xf,0x1e00780,0x1e0,0x7c000f00,0x78000f,
      0x78007,0xff1e0000,0x0,0x3ff00,0x0,0x1ff,0x8003c000,0x781e0070,0x3c007803,0xc000f03c,0x1e0,0x3c03c0,0x1e00,0x3c000,0x781e0007,
      0x80007800,0x780,0x3c07c000,0x7800001e,0x7c0f078,0xf1e03c0,0x780780,0xf000,0x1e07801f,0x3e,0xf000,0xf0003c0,0x781e003,0xcf1c7800,
      0x3cf80000,0x7800000f,0x8000003c,0xf0,0x1e0,0x0,0x0,0x0,0x3ffffc01,0xe000780f,0x1e0,0x780fff,0xffe0003c,0x3c000,0xf0078007,
      0x80003c00,0xf000,0x7ff8000,0x1e0000f,0x3c0f01e,0x1e03c0,0xf00780,0x1e0f000,0x3c003c00,0x3fff0,0x3c000,0x3c003c0,0xf81f003,
      0xc3b87800,0xf80000f,0x1e00001,0xf0000ff0,0xf0,0xff,0xf03fff,0x80000000,0x3fff8001,0xfff1ff00,0x0,0xf000000,0x0,0x18,0xc0,
      0x0,0x380000,0x7c0,0x3c00003,0xc00f001f,0xff1ff000,0xf80007ff,0xc7fc3ffe,0x3fe00000,0x0,0x0,0x0,0x1ff000,0x7ffffe1f,0xffffff00,
      0x0,0x7f,0xfe000000,0x780f007f,0xff1fff1f,0xf0001f00,0x1e000,0x0,0x780001,0xe0180000,0xf000001c,0xe00fff,0xffc00000,0x7c00,
      0xf0000000,0x31c0001,0x80ffff80,0x3e078000,0x1e00,0x7ff80183,0x81c0c000,0x0,0x78,0x0,0x0,0x0,0x3c003c0,0xe1c00,0x3f00000,
      0x0,0x7f,0xff00001e,0x7c7ff03,0xc03ff8fe,0x1ffc0f0,0x7e0000,0x7800f00,0x3c007801,0xe003c00f,0x1e0078,0xf003c0,0x7803c07,0x8003c000,
      0x78000,0x3c0000,0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001,0xe0001e00,0x3c0f01e,0x3c078000,0xf03c0007,0x81e0003c,
      0xf0001e0,0x78000f00,0x3fc001e,0x7803c0f0,0x3c0780,0x1e03c00,0xf01e000,0x78000780,0x1e007f,0xf03fe0,0x7ffff80,0x3ffffc01,
      0xffffe00f,0xffff007f,0xfff803ff,0xffc07fff,0xffc0f000,0x1fffff,0xc0fffffe,0x7fffff0,0x3fffff80,0x780000,0x3c00000,0x1e000000,
      0xf0001e00,0x7803c00,0x3c078001,0xe03c000f,0x1e00078,0xf0003c0,0x78001e07,0xfffffe1e,0x707803c,0x3c01e0,0x1e00f00,0xf007800,
      0x7800f01e,0x1e0007,0x803c0780,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x1ff,0xffff8000,0x30f81f00,0xffe1e00f,0x87800000,0x3c00,0x1e00,0x0,0x1e0000,0x0,0x7ff80000,
      0x7,0x80001e00,0x3c0001e,0x1f8,0x7,0x83c00780,0x1e0,0x7c000f00,0xf8001e,0x3c001,0xfc1e0000,0x0,0x7fe0,0x0,0xffc,0x3c000,0x781e0070,
      0x3ffff803,0xc000783c,0x1e0,0x3c03c0,0x1e00,0x3c000,0x781e0007,0x80007800,0x780,0x3c07c000,0x7800001e,0x380f078,0xf1e03c0,
      0x780780,0xf000,0x1e07800f,0x8000001e,0xf000,0xf0003c0,0x3c3c003,0xcf1e7800,0x7c780000,0x7800000f,0x8000003c,0xf0,0x1e0,0x0,
      0x0,0x0,0x7f003c01,0xe000780f,0x1e0,0x780fff,0xffe0003c,0x3c000,0xf0078007,0x80003c00,0xf000,0x7f7c000,0x1e0000f,0x3c0f01e,
      0x1e03c0,0xf00780,0x1e0f000,0x3c003c00,0xfff8,0x3c000,0x3c003c0,0x781e003,0xc3b87800,0x1fc00007,0x83e00003,0xe0000ff8,0xf0,
      0x1ff,0xc007fe,0x0,0x7fff8001,0xffe3ff00,0x0,0x1e000000,0x0,0x18,0xc0,0x0,0x0,0x3c0,0x7800003,0xc00f001f,0xfe3ff000,0xf80007ff,
      0x8ffc3ffc,0x7fe00000,0x0,0x0,0x0,0x1ff000,0x0,0x0,0x0,0x1f,0xff000000,0x3c0f007f,0xff1ffe3f,0xf0003e00,0x1e000,0x0,0x780001,
      0xe0180000,0xf000001e,0x1e00fff,0xffc00000,0x3f00,0xf0000000,0x31c0001,0x80ffff80,0x1f03c000,0x1e00,0x7ff80183,0x81c0c000,
      0x0,0x78,0x0,0x0,0x0,0x3c003c0,0xe1c00,0x0,0x0,0x7f,0xff00003c,0xf87f007,0xc03f83ff,0x81fc01f0,0x7c0000,0x7ffff00,0x3ffff801,
      0xffffc00f,0xfffe007f,0xfff003ff,0xff807fff,0x8003c000,0x78000,0x3c0000,0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001,
      0xe0001e00,0x3c0f01e,0x3c078000,0xf03c0007,0x81e0003c,0xf0001e0,0x78000f00,0x7fe001e,0xf003c0f0,0x3c0780,0x1e03c00,0xf01e000,
      0x78000780,0x1ffffe,0xf00ff0,0xfe00780,0x7f003c03,0xf801e01f,0xc00f00fe,0x7807f0,0x3c0ffff,0xffc0f000,0x1fffff,0xc0fffffe,
      0x7fffff0,0x3fffff80,0x780000,0x3c00000,0x1e000000,0xf0001e00,0x7803c00,0x3c078001,0xe03c000f,0x1e00078,0xf0003c0,0x78001e00,
      0x1e,0xf07803c,0x3c01e0,0x1e00f00,0xf007800,0x7800783e,0x1e0007,0x801e0f80,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x1ff,0xffff8000,0x307c0801,0xe1f1e00f,0x87000000,
      0x3c00,0x1e00,0x0,0x1e0000,0x0,0x7ff80000,0xf,0x1e00,0x3c0001e,0x3f0,0x7,0x83fffffc,0x1e0,0x7c000f00,0xf0001e,0x3c000,0x3e0000,
      0x0,0x1ffc,0x1fffff,0xf0007ff0,0x3c000,0x781e0070,0x7ffffc03,0xc000781e,0x1e0,0x7803c0,0x1e00,0x3c000,0x781e0007,0x80007800,
      0x780,0x3c03e000,0x7800001e,0xf078,0x79e03c0,0x780780,0xf000,0x1e078007,0x8000000f,0xf000,0xf0003c0,0x3c3c001,0xee0ef000,
      0xf87c0000,0x7800001f,0x3c,0x78,0x1e0,0x0,0x0,0x0,0x7c003c01,0xe000780f,0x1e0,0x780f00,0x3c,0x3c000,0xf0078007,0x80003c00,
      0xf000,0x7e3e000,0x1e0000f,0x3c0f01e,0x1e03c0,0xf00780,0x1e0f000,0x3c003c00,0x1ffc,0x3c000,0x3c003c0,0x781e003,0xe3b8f800,
      0x1fc00007,0x83c00007,0xc00000fc,0xf0,0x3e0,0x8001f8,0x0,0x7800000,0xffc7fe00,0x0,0x1e000000,0x0,0x18,0xc0,0x0,0x0,0x1e0,
      0xf000003,0xc00f000f,0xfc7fe001,0xf00003ff,0x1ff81ff8,0xffc00000,0x0,0x0,0x0,0x1ff000,0x0,0x0,0x0,0x3,0xff800000,0x1e0f0078,
      0xffc7f,0xe0007c00,0x1e000,0x0,0x780001,0xe0180000,0xf000000e,0x1c00007,0x80000000,0x1f81,0xe0000000,0x38e0003,0x80000000,
      0xf81f000,0x1e00,0x7ff801c3,0x80e1c000,0x0,0x78,0x0,0x0,0x0,0x3c003c0,0xe1c00,0x0,0x0,0x0,0xf8,0x1f070007,0xc03803ff,0xc1c001f0,
      0xf80000,0xfffff00,0x7ffff803,0xffffc01f,0xfffe00ff,0xfff007ff,0xffc07fff,0x8001e000,0x78000,0x3c0000,0x1e00000,0xf000000,
      0xf00000,0x7800000,0x3c000001,0xe0001e00,0x780f00f,0x3c078000,0xf03c0007,0x81e0003c,0xf0001e0,0x78000f00,0xf9f001e,0xf003c0f0,
      0x3c0780,0x1e03c00,0xf01e000,0x78000780,0x1ffffc,0xf003f8,0xf800780,0x7c003c03,0xe001e01f,0xf00f8,0x7807c0,0x3c0fc1e,0xf000,
      0x1e0000,0xf00000,0x7800000,0x3c000000,0x780000,0x3c00000,0x1e000000,0xf0001e00,0x7803c00,0x3c078001,0xe03c000f,0x1e00078,
      0xf0003c0,0x78001e00,0x1e,0x1e07803c,0x3c01e0,0x1e00f00,0xf007800,0x7800783c,0x1e0007,0x801e0f00,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1ff,0xffff8000,0x303c0001,
      0xc071e007,0xcf000000,0x3c00,0x1e00,0x0,0x1e0000,0x0,0x0,0xf,0xf00,0x780001e,0x7e0,0x7,0x83fffffc,0x1e0,0x7c000f00,0x1f0001e,
      0x3c000,0x3c0000,0x0,0x3ff,0x801fffff,0xf003ff80,0x3c000,0x781e0070,0x7ffffc03,0xc000781e,0x1e0,0x7803c0,0x1e00,0x1e000,0x781e0007,
      0x80007800,0x780,0x3c01f000,0x7800001e,0xf078,0x79e03c0,0xf00780,0xf000,0x3e078007,0xc000000f,0xf000,0xf0003c0,0x3c3c001,
      0xee0ef000,0xf03e0000,0x7800003e,0x3c,0x78,0x1e0,0x0,0x0,0x0,0xf8003c01,0xe000780f,0x1e0,0x780f00,0x3c,0x3c000,0xf0078007,
      0x80003c00,0xf000,0x7c3e000,0x1e0000f,0x3c0f01e,0x1e03c0,0xf00780,0x1e0f000,0x3c003c00,0xfc,0x3c000,0x3c003c0,0x3c3e001,0xe7b8f000,
      0x3fe00007,0xc7c0000f,0xc000003e,0xf0,0x7c0,0x0,0x0,0x7c00000,0x7fcffc00,0x0,0x1e000000,0x0,0x18,0xc0,0x0,0x0,0x1e0,0x1e000003,
      0xc00f0007,0xfcffc003,0xe00001ff,0x3ff00ff9,0xff800000,0x0,0x0,0x0,0x1ff000,0x0,0x0,0x0,0x0,0x1f800000,0xf0f0078,0x7fcff,
      0xc000fc00,0x1e000,0x0,0x780001,0xe0180000,0xf000000f,0x87c00007,0x80000000,0xfe3,0xe0000000,0x18780c3,0x0,0x7c0f800,0x1e00,
      0xc3,0x80e18000,0x0,0x78,0x0,0x0,0x0,0x3c003c0,0xe1c00,0x0,0x0,0x0,0x1f0,0x3e00000f,0xc0000303,0xe00003f0,0xf00000,0xfffff80,
      0x7ffffc03,0xffffe01f,0xffff00ff,0xfff807ff,0xffc07fff,0x8001e000,0x78000,0x3c0000,0x1e00000,0xf000000,0xf00000,0x7800000,
      0x3c000001,0xe0001e00,0x780f00f,0x3c078001,0xe03c000f,0x1e00078,0xf0003c0,0x78001e00,0x1f0f801f,0xe00780f0,0x3c0780,0x1e03c00,
      0xf01e000,0x78000780,0x1ffff8,0xf000f8,0x1f000780,0xf8003c07,0xc001e03e,0xf01f0,0x780f80,0x3c1f01e,0xf000,0x1e0000,0xf00000,
      0x7800000,0x3c000000,0x780000,0x3c00000,0x1e000000,0xf0001e00,0x7803c00,0x3c078001,0xe03c000f,0x1e00078,0xf0003c0,0x78001e00,
      0x1e,0x3c07803c,0x3c01e0,0x1e00f00,0xf007800,0x78007c7c,0x1e0007,0x801f1f00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7,0x81c00000,0x303c0003,0x8039e003,0xef000000,
      0x3c00,0x1e00,0x0,0x1e0000,0x0,0x0,0x1e,0xf00,0x780001e,0xfc0,0x7,0x83fffffc,0x1e0,0x3c000f00,0x1e0001e,0x3c000,0x3c0000,
      0x0,0x7f,0xe01fffff,0xf00ffc00,0x3c000,0x781f00f0,0x7ffffc03,0xc000781e,0x1e0,0x7803c0,0x1e00,0x1e000,0x781e0007,0x80007800,
      0x780,0x3c01f000,0x7800001e,0xf078,0x7de01e0,0xf00780,0x7800,0x3c078003,0xc000000f,0xf000,0xf0003c0,0x3e7c001,0xee0ef001,
      0xf01e0000,0x7800003e,0x3c,0x3c,0x1e0,0x0,0x0,0x0,0xf0003c01,0xe000780f,0x1e0,0x780f00,0x3c,0x3c000,0xf0078007,0x80003c00,
      0xf000,0x781f000,0x1e0000f,0x3c0f01e,0x1e03c0,0xf00780,0x1e0f000,0x3c003c00,0x3e,0x3c000,0x3c003c0,0x3c3c001,0xe71cf000,0x7df00003,
      0xc780000f,0x8000003e,0xf0,0x780,0x0,0x0,0x3c00000,0x3fcff800,0x0,0x1e000000,0x0,0x18,0xc0,0x0,0x1f00fc,0x1e0,0x1e000001,
      0xe00f0003,0xfcff8003,0xe00000ff,0x3fe007f9,0xff000000,0x0,0x0,0x0,0x1ff000,0x0,0x0,0x0,0x0,0x7c00000,0xf0f0078,0x3fcff,0x8000f800,
      0x1e000,0x0,0x780001,0xe0180000,0xf000001f,0xffe00007,0x8000003c,0x7ff,0xc0000000,0x1c3ffc7,0x0,0x3e07c00,0x1e00,0xe3,0x80738000,
      0x0,0x78,0x0,0x0,0x0,0x3c003c0,0xe1c00,0x0,0x0,0x0,0x3e0,0x7c00001d,0xc0000001,0xe0000770,0x1f00000,0xfffff80,0x7ffffc03,
      0xffffe01f,0xffff00ff,0xfff807ff,0xffc07fff,0x8001e000,0x78000,0x3c0000,0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001,
      0xe0001e00,0x780f00f,0x3c03c001,0xe01e000f,0xf00078,0x78003c0,0x3c001e00,0x3e07c01f,0xc00780f0,0x3c0780,0x1e03c00,0xf01e000,
      0x78000780,0x1fffc0,0xf0007c,0x1e000780,0xf0003c07,0x8001e03c,0xf01e0,0x780f00,0x3c1e01e,0xf000,0x1e0000,0xf00000,0x7800000,
      0x3c000000,0x780000,0x3c00000,0x1e000000,0xf0001e00,0x7803c00,0x3c078001,0xe03c000f,0x1e00078,0xf0003c0,0x78001e00,0x1e,0x7807803c,
      0x3c01e0,0x1e00f00,0xf007800,0x78003c78,0x1e0007,0x800f1e00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7,0x83c00000,0x303c0003,0x8039e001,0xee000000,0x1e00,0x3c00,
      0x0,0x1e0000,0x0,0x0,0x1e,0xf00,0x780001e,0x1f80,0x7,0x83fffffc,0x1e0,0x3c000f00,0x1e0001e,0x3c000,0x3c0000,0x0,0x1f,0xfc1fffff,
      0xf07ff000,0x0,0x780f00f0,0x78003c03,0xc000781e,0x1e0,0xf803c0,0x1e00,0x1e000,0x781e0007,0x80007800,0x780,0x3c00f800,0x7800001e,
      0xf078,0x3de01e0,0xf00780,0x7800,0x3c078003,0xe000000f,0xf000,0xf0003c0,0x1e78001,0xfe0ff003,0xe01f0000,0x7800007c,0x3c,0x3c,
      0x1e0,0x0,0x0,0x0,0xf0007c01,0xe000f80f,0x800001e0,0xf80f00,0x3c,0x1e001,0xf0078007,0x80003c00,0xf000,0x780f800,0x1e0000f,
      0x3c0f01e,0x1e03c0,0x1f00780,0x3e0f000,0x7c003c00,0x1e,0x3c000,0x3c003c0,0x3c3c001,0xe71cf000,0xf8f80003,0xe780001f,0x1e,
      0xf0,0x780,0x0,0x0,0x3c00000,0x1ffff000,0x0,0x1e000000,0x0,0x18,0xc0,0x0,0x3bc1de,0x1e0,0xf000001,0xe00f0001,0xffff0007,0xc000007f,
      0xffc003ff,0xfe000000,0x0,0x0,0x0,0xfe000,0x0,0x0,0x0,0x0,0x3c00000,0x1e0f0078,0x1ffff,0x1f000,0x1e000,0x0,0x780000,0xf0180000,
      0xf000001f,0xfff00007,0x8000003c,0x1ff,0x80000000,0xe0ff0e,0x0,0x1f03e00,0x1e00,0x70,0x70000,0x0,0x78,0x0,0x0,0x0,0x3c003c0,
      0xe1c00,0x0,0x0,0x0,0x7c0,0xf8000019,0xc0000000,0xe0000670,0x1e00000,0xf000780,0x78003c03,0xc001e01e,0xf00f0,0x780780,0x3c0f807,
      0x8001e000,0x78000,0x3c0000,0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001,0xe0001e00,0xf80f007,0xbc03c001,0xe01e000f,
      0xf00078,0x78003c0,0x3c001e00,0x7c03e00f,0x800780f0,0x3c0780,0x1e03c00,0xf01e000,0x78000780,0x1e0000,0xf0003c,0x1e000f80,
      0xf0007c07,0x8003e03c,0x1f01e0,0xf80f00,0x7c1e01e,0xf800,0x1e0000,0xf00000,0x7800000,0x3c000000,0x780000,0x3c00000,0x1e000000,
      0xf0001e00,0x7803c00,0x3c078003,0xe03c001f,0x1e000f8,0xf0007c0,0x78003e00,0x1f8001f,0xf00f803c,0x3c01e0,0x1e00f00,0xf007800,
      0x78003e78,0x1e000f,0x800f9e00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xf,0x3c00000,0x303c0003,0x8039f001,0xfe000000,0x1e00,0x3c00,0x0,0x1e0000,0x0,0x0,0x3c,0xf00,
      0x780001e,0x3f00,0x7,0x80000780,0x3e0,0x3e000f00,0x3c0001e,0x3c000,0x7c0000,0x0,0x3,0xfe000000,0xff8000,0x0,0x3c0f81f0,0xf0001e03,
      0xc000780f,0x1e0,0xf003c0,0x1e00,0xf000,0x781e0007,0x80007800,0x780,0x3c007c00,0x7800001e,0xf078,0x3de01e0,0xf00780,0x7800,
      0x3c078001,0xe000000f,0xf000,0xf0003c0,0x1e78001,0xfc07f003,0xe00f0000,0x78000078,0x3c,0x1e,0x1e0,0x0,0x0,0x0,0xf0007c01,
      0xf000f007,0x800000f0,0xf80780,0x3c,0x1e001,0xf0078007,0x80003c00,0xf000,0x7807c00,0x1e0000f,0x3c0f01e,0x1e01e0,0x1e007c0,
      0x3c07800,0x7c003c00,0x1e,0x3c000,0x3c007c0,0x1e78001,0xe71df000,0xf8f80001,0xef80003e,0x1e,0xf0,0x780,0x0,0x0,0x3c00000,
      0xfffe000,0x0,0x3e000000,0x0,0x18,0x7fff,0xc0000000,0x60c306,0x1e0,0x7800001,0xe00f0000,0xfffe0007,0x8000003f,0xff8001ff,
      0xfc000000,0x0,0x0,0x0,0x7c000,0x0,0x0,0x0,0x0,0x3c00000,0x3c0f0078,0xfffe,0x3e000,0x1e000,0x0,0x780000,0xf0180000,0xf000003c,
      0xfcf80007,0x8000003c,0x7f,0x0,0x70001c,0x0,0xf81f00,0x0,0x38,0xe0000,0x0,0x0,0x0,0x0,0x0,0x3c003c0,0xe1c00,0x0,0x0,0x0,0xf81,
      0xf0000039,0xc0000000,0xe0000e70,0x1e00000,0x1e0003c0,0xf0001e07,0x8000f03c,0x781e0,0x3c0f00,0x1e0f007,0x8000f000,0x78000,
      0x3c0000,0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001,0xe0001e00,0xf00f007,0xbc03c001,0xe01e000f,0xf00078,0x78003c0,
      0x3c001e00,0xf801f00f,0x800780f0,0x3c0780,0x1e03c00,0xf01e000,0x78000780,0x1e0000,0xf0003c,0x1e000f80,0xf0007c07,0x8003e03c,
      0x1f01e0,0xf80f00,0x7c1e01e,0x7800,0xf0000,0x780000,0x3c00000,0x1e000000,0x780000,0x3c00000,0x1e000000,0xf0000f00,0xf003c00,
      0x3c03c003,0xc01e001e,0xf000f0,0x7800780,0x3c003c00,0x1f8000f,0xe00f003c,0x7c01e0,0x3e00f00,0x1f007800,0xf8001ef8,0x1f000f,
      0x7be00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0xf,0x3c00000,0x307c0003,0x8038f000,0xfc000000,0x1e00,0x3c00,0x0,0x1e0000,0xfc0000,0x0,0x7e00003c,0x780,0xf00001e,
      0x7e00,0xf,0x80000780,0x3c0,0x3e001e00,0x3c0001f,0x7c000,0x780007,0xe000003f,0x0,0xfe000000,0xfe0000,0x0,0x3c07c3f0,0xf0001e03,
      0xc000f80f,0x800001e0,0x1f003c0,0x1e00,0xf000,0x781e0007,0x80007800,0x4000f80,0x3c003c00,0x7800001e,0xf078,0x1fe01f0,0x1f00780,
      0x7c00,0x7c078001,0xf000001f,0xf000,0xf0003c0,0x1e78001,0xfc07f007,0xc00f8000,0x780000f8,0x3c,0x1e,0x1e0,0x0,0x0,0x0,0xf0007c01,
      0xf000f007,0xc00000f0,0xf80780,0x3c,0x1f003,0xf0078007,0x80003c00,0xf000,0x7807c00,0x1e0000f,0x3c0f01e,0x1e01e0,0x1e007c0,
      0x3c07800,0x7c003c00,0x1e,0x3c000,0x3c007c0,0x1e78000,0xfe0fe001,0xf07c0001,0xef00007c,0x1e,0xf0,0x780,0x0,0x0,0x1e00000,
      0x7cfc000,0xfc00000,0x3c00000f,0xc3f00000,0x18,0x7fff,0xc0000000,0x406303,0x3e0,0x3c00001,0xf00f0000,0x7cfc000f,0x8000001f,
      0x3f0000f9,0xf8000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c00000,0x780700f8,0x7cfc,0x7c000,0x1e000,0x0,0x780000,0xf8180000,
      0xf0000070,0x3c0007,0x8000003c,0x3f,0x80000000,0x3c0078,0x0,0x780f00,0x0,0x1e,0x3c0000,0x0,0x0,0x0,0x0,0x0,0x3e007c0,0xe1c00,
      0x0,0x0,0x0,0xf01,0xe0000071,0xc0000000,0xe0001c70,0x1e00000,0x1e0003c0,0xf0001e07,0x8000f03c,0x781e0,0x3c0f00,0x1e0f007,
      0x8000f800,0x78000,0x3c0000,0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001,0xe0001e00,0x1f00f003,0xfc03e003,0xe01f001f,
      0xf800f8,0x7c007c0,0x3e003e01,0xf000f80f,0xf00f0,0x3c0780,0x1e03c00,0xf01e000,0x78000780,0x1e0000,0xf0003c,0x1e000f80,0xf0007c07,
      0x8003e03c,0x1f01e0,0xf80f00,0x7c1e01e,0x7c00,0xf0000,0x780000,0x3c00000,0x1e000000,0x780000,0x3c00000,0x1e000000,0xf0000f00,
      0xf003c00,0x3c03c003,0xc01e001e,0xf000f0,0x7800780,0x3c003c00,0x1f8000f,0xc00f003c,0x7c01e0,0x3e00f00,0x1f007800,0xf8001ef0,
      0x1f000f,0x7bc00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x780000,0xf,0x3800040,0x30780003,0x8038f800,0x78000000,0x1e00,0x3c00,0x0,0x1e0000,0xfc0000,0x0,0x7e000078,
      0x780,0x1f00001e,0xfc00,0x20001f,0x780,0x80007c0,0x1f001e00,0x7c0000f,0x78000,0xf80007,0xe000003f,0x0,0x1e000000,0xf00000,
      0x3c000,0x3c03fff0,0xf0001e03,0xc001f007,0x800101e0,0x7e003c0,0x1e00,0x7800,0x781e0007,0x80007800,0x6000f00,0x3c003e00,0x7800001e,
      0xf078,0x1fe00f0,0x1e00780,0x3c00,0x78078000,0xf020001e,0xf000,0x7800780,0xff0001,0xfc07f00f,0x8007c000,0x780001f0,0x3c,0xf,
      0x1e0,0x0,0x0,0x0,0xf800fc01,0xf801f007,0xc00100f8,0x1f807c0,0x40003c,0xf807,0xf0078007,0x80003c00,0xf000,0x7803e00,0x1f0000f,
      0x3c0f01e,0x1e01f0,0x3e007e0,0x7c07c00,0xfc003c00,0x1e,0x3e000,0x3e007c0,0x1ff8000,0xfe0fe003,0xe03e0001,0xff0000fc,0x1e,
      0xf0,0x780,0x0,0x0,0x1f00080,0x3cf8000,0xfc00000,0x3c00001f,0x83f00000,0x18,0xc0,0x0,0xc06203,0x40003c0,0x1c00000,0xf80f0000,
      0x3cf8001f,0xf,0x3e000079,0xf0000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c00000,0x700780fc,0x3cf8,0xfc000,0x1e000,0x0,0x780000,
      0x7c180000,0xf0000020,0x100007,0x8000003c,0xf,0x80000000,0x1f01f0,0x0,0x380700,0x0,0xf,0x80f80000,0x0,0x0,0x0,0x0,0x0,0x3e007c0,
      0xe1c00,0x0,0x0,0x0,0xe01,0xc0000071,0xc0000001,0xc0001c70,0x1e00040,0x1e0003c0,0xf0001e07,0x8000f03c,0x781e0,0x3c0f00,0x1e0f007,
      0x80007800,0x10078000,0x3c0000,0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001,0xe0001e00,0x7e00f003,0xfc01e003,0xc00f001e,
      0x7800f0,0x3c00780,0x1e003c00,0xe000700f,0x800f0078,0x7803c0,0x3c01e00,0x1e00f000,0xf0000780,0x1e0000,0xf0003c,0x1f001f80,
      0xf800fc07,0xc007e03e,0x3f01f0,0x1f80f80,0xfc1e01f,0x7c00,0x100f8000,0x807c0004,0x3e00020,0x1f000100,0x780000,0x3c00000,0x1e000000,
      0xf0000f80,0x1f003c00,0x3c03e007,0xc01f003e,0xf801f0,0x7c00f80,0x3e007c00,0x1f8000f,0x801f003e,0x7c01f0,0x3e00f80,0x1f007c00,
      0xf8001ff0,0x1f801f,0x7fc00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0xf,0x7800078,0x31f80001,0xc070fc00,0xfc000000,0x1e00,0x7c00,0x0,0x1e0000,0xfc0000,0x0,0x7e000078,
      0x7c0,0x1f00001e,0x1f000,0x38003f,0x780,0xe000f80,0x1f803e00,0x780000f,0x800f8000,0x1f00007,0xe000003f,0x0,0x2000000,0x800000,
      0x3c000,0x3e01ff71,0xf0001f03,0xc007f007,0xc00301e0,0x1fc003c0,0x1e00,0x7c00,0x781e0007,0x80007800,0x7801f00,0x3c001f00,0x7800001e,
      0xf078,0xfe00f8,0x3e00780,0x3e00,0xf8078000,0xf838003e,0xf000,0x7c00f80,0xff0000,0xfc07e00f,0x8003c000,0x780001e0,0x3c,0xf,
      0x1e0,0x0,0x0,0x0,0xf801fc01,0xfc03e003,0xe003007c,0x3f803e0,0x1c0003c,0xfc0f,0xf0078007,0x80003c00,0xf000,0x7801f00,0xf8000f,
      0x3c0f01e,0x1e00f8,0x7c007f0,0xf803e01,0xfc003c00,0x8003e,0x1f000,0x1e00fc0,0xff0000,0xfe0fe007,0xc01f0000,0xfe0000f8,0x1e,
      0xf0,0x780,0x0,0x0,0xf80180,0x1cf0000,0x1f800000,0x3c00001f,0x83e00000,0x18,0xc0,0x0,0xc06203,0x70007c0,0xe00000,0x7e0f0000,
      0x1cf0001e,0x7,0x3c000039,0xe0000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x100,0x7c00000,0xe00780fc,0x2001cf0,0xf8000,0x1e000,0x0,
      0x780000,0x7e182000,0xf0000000,0x7,0x8000003c,0x7,0xc0000000,0x7ffc0,0x0,0x180300,0x0,0x3,0xffe00000,0x0,0x0,0x0,0x0,0x0,
      0x3f00fc0,0xe1c00,0x0,0x0,0x0,0xc01,0x800000e1,0xc0000003,0xc0003870,0x1f001c0,0x3e0003e1,0xf0001f0f,0x8000f87c,0x7c3e0,0x3e1f00,
      0x1f1e007,0x80007c00,0x30078000,0x3c0000,0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001,0xe0001e03,0xfc00f001,0xfc01f007,
      0xc00f803e,0x7c01f0,0x3e00f80,0x1f007c00,0x4000201f,0xc01f007c,0xf803e0,0x7c01f00,0x3e00f801,0xf0000780,0x1e0000,0xf0007c,
      0x1f003f80,0xf801fc07,0xc00fe03e,0x7f01f0,0x3f80f80,0x1fc1f03f,0x803e00,0x3007c003,0x803e001c,0x1f000e0,0xf800700,0x780000,
      0x3c00000,0x1e000000,0xf00007c0,0x3e003c00,0x3c01f00f,0x800f807c,0x7c03e0,0x3e01f00,0x1f00f800,0x1f80007,0xc03e001e,0xfc00f0,
      0x7e00780,0x3f003c01,0xf8000fe0,0x1fc03e,0x3f800,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x1e,0x780007f,0xfff00001,0xe0f07f03,0xfe000000,0xf00,0x7800,0x0,
      0x1e0000,0xfc0000,0x0,0x7e0000f0,0x3f0,0x7e000fff,0xfc03ffff,0xf83f00fe,0x780,0xfc03f80,0xfc0fc00,0xf800007,0xe03f0018,0x7e00007,
      0xe000003f,0x0,0x0,0x0,0x3c000,0x1e007c71,0xe0000f03,0xffffe003,0xf01f01ff,0xff8003ff,0xffe01e00,0x3f01,0xf81e0007,0x803ffff0,
      0x7e03f00,0x3c000f00,0x7ffffe1e,0xf078,0xfe007e,0xfc00780,0x1f83,0xf0078000,0x783f00fe,0xf000,0x3f03f00,0xff0000,0xfc07e01f,
      0x3e000,0x780003ff,0xfffc003c,0x7,0x800001e0,0x0,0x0,0x0,0x7e07fc01,0xfe07e001,0xf80f007e,0x7f801f8,0xfc0003c,0x7ffe,0xf0078007,
      0x807ffffe,0xf000,0x7801f00,0xfff00f,0x3c0f01e,0x1e00fc,0xfc007f8,0x1f803f03,0xfc003c00,0xf80fc,0x1fff0,0x1f83fc0,0xff0000,
      0xfc07e007,0xc01f0000,0xfe0001ff,0xffe0001e,0xf0,0x780,0x0,0x0,0xfe0780,0xfe0000,0x1f000000,0x3c00001f,0x7c00e03,0x81c00018,
      0xc0,0x0,0x406203,0x7e01fc0,0x700000,0x7fffff80,0xfe0003f,0xffffc003,0xf800001f,0xc0000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1f0,
      0x1f800001,0xc007c1fe,0x6000fe0,0x1ffffe,0x1e000,0x0,0x780000,0x3f98e03f,0xffff8000,0x7,0x8000003c,0x7,0xc0000000,0xfe00,
      0x0,0x80100,0x0,0x0,0x7f000000,0x0,0x1ffff,0xfe000000,0x0,0x0,0x3f83fe8,0xe1c00,0x0,0x0,0x0,0x801,0xc1,0xc0000007,0x80003070,
      0xfc0fc0,0x3c0001e1,0xe0000f0f,0x7878,0x3c3c0,0x1e1e00,0xf1e007,0xffc03f01,0xf007ffff,0xc03ffffe,0x1fffff0,0xfffff80,0x7fffe003,
      0xffff001f,0xfff800ff,0xffc01fff,0xf800f001,0xfc00fc1f,0x8007e0fc,0x3f07e0,0x1f83f00,0xfc1f800,0x1f,0xf07e003f,0x3f001f8,
      0x1f800fc0,0xfc007e07,0xe0000780,0x1e0000,0xf301f8,0xfc0ff80,0x7e07fc03,0xf03fe01f,0x81ff00fc,0xff807e0,0x7fc0f87f,0x81801f80,
      0xf003f01f,0x801f80fc,0xfc07e0,0x7e03f00,0xfffffc07,0xffffe03f,0xffff01ff,0xfff807e0,0x7e003c00,0x3c01f81f,0x800fc0fc,0x7e07e0,
      0x3f03f00,0x1f81f800,0x1f8000f,0xe07e001f,0x83fc00fc,0x1fe007e0,0xff003f07,0xf8000fe0,0x1fe07e,0x3f800,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x1e,0x780007f,
      0xffe00000,0xffe03fff,0xdf000000,0xf00,0x7800,0x0,0x0,0xfc0000,0x0,0x7e0000f0,0x1ff,0xfc000fff,0xfc03ffff,0xf83ffffc,0x780,
      0xfffff00,0x7fff800,0xf000007,0xffff001f,0xffe00007,0xe000003f,0x0,0x0,0x0,0x3c000,0x1e000001,0xe0000f03,0xffffc001,0xffff01ff,
      0xff0003ff,0xffe01e00,0x1fff,0xf81e0007,0x803ffff0,0x7fffe00,0x3c000f80,0x7ffffe1e,0xf078,0xfe003f,0xff800780,0xfff,0xf0078000,
      0x7c3ffffc,0xf000,0x3ffff00,0xff0000,0xf803e01e,0x1e000,0x780003ff,0xfffc003c,0x7,0x800001e0,0x0,0x0,0x0,0x7fffbc01,0xffffc000,
      0xffff003f,0xfff800ff,0xffc0003c,0x3ffe,0xf0078007,0x807ffffe,0xf000,0x7800f80,0x7ff00f,0x3c0f01e,0x1e007f,0xff8007ff,0xff001fff,
      0xbc003c00,0xffffc,0x1fff0,0x1fffbc0,0xff0000,0x7c07c00f,0x800f8000,0x7e0001ff,0xffe0001e,0xf0,0x780,0x0,0x0,0x7fff80,0x7c0000,
      0x1f000000,0x3c00001e,0x7c00f07,0xc1e00018,0xc0,0x0,0x60e303,0x7ffff80,0x380000,0x3fffff80,0x7c0003f,0xffffc001,0xf000000f,
      0x80000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1ff,0xff800003,0x8003ffff,0xfe0007c0,0x1ffffe,0x1e000,0x0,0x780000,0x1fffe03f,0xffff8000,
      0x7,0x8000003c,0x3,0xc0000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1ffff,0xfe000000,0x0,0x0,0x3fffdf8,0xe1c00,0x0,0x0,0x0,0x0,0x1c1,
      0xc000000f,0x7070,0x7fffc0,0x3c0001e1,0xe0000f0f,0x7878,0x3c3c0,0x1e1e00,0xf1e007,0xffc01fff,0xf007ffff,0xc03ffffe,0x1fffff0,
      0xfffff80,0x7fffe003,0xffff001f,0xfff800ff,0xffc01fff,0xf000f001,0xfc007fff,0x3fff8,0x1fffc0,0xfffe00,0x7fff000,0x3b,0xfffc003f,
      0xfff001ff,0xff800fff,0xfc007fff,0xe0000780,0x1e0000,0xf3fff8,0xffff780,0x7fffbc03,0xfffde01f,0xffef00ff,0xff7807ff,0xfbc0ffff,
      0xff800fff,0xf001ffff,0x800ffffc,0x7fffe0,0x3ffff00,0xfffffc07,0xffffe03f,0xffff01ff,0xfff803ff,0xfc003c00,0x3c00ffff,0x7fff8,
      0x3fffc0,0x1fffe00,0xffff000,0x1f,0xfffc001f,0xffbc00ff,0xfde007ff,0xef003fff,0x780007e0,0x1ffffc,0x1f800,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x1e,0x700003f,
      0xffc00000,0x7fc01fff,0x9f800000,0xf80,0xf800,0x0,0x0,0xfc0000,0x0,0x7e0000f0,0xff,0xf8000fff,0xfc03ffff,0xf83ffff8,0x780,
      0xffffe00,0x7fff000,0xf000003,0xfffe001f,0xffc00007,0xe000003f,0x0,0x0,0x0,0x3c000,0xf000003,0xe0000f83,0xffff0000,0xffff01ff,
      0xfc0003ff,0xffe01e00,0xfff,0xf01e0007,0x803ffff0,0x7fffc00,0x3c0007c0,0x7ffffe1e,0xf078,0x7e003f,0xff000780,0x7ff,0xe0078000,
      0x3c3ffff8,0xf000,0x1fffe00,0x7e0000,0xf803e03e,0x1f000,0x780003ff,0xfffc003c,0x7,0x800001e0,0x0,0x0,0x0,0x3fff3c01,0xefff8000,
      0x7ffe001f,0xff78007f,0xff80003c,0x1ffc,0xf0078007,0x807ffffe,0xf000,0x78007c0,0x3ff00f,0x3c0f01e,0x1e003f,0xff0007bf,0xfe000fff,
      0xbc003c00,0xffff8,0xfff0,0xfff3c0,0x7e0000,0x7c07c01f,0x7c000,0x7c0001ff,0xffe0001e,0xf0,0x780,0x0,0x0,0x3fff80,0x380000,
      0x3e000000,0x7c00003e,0x7801f07,0xc1e00018,0xc0,0x0,0x39c1ce,0x7ffff00,0x1c0000,0xfffff80,0x380003f,0xffffc000,0xe0000007,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1ff,0xff000007,0x1ffcf,0xfe000380,0x1ffffe,0x1e000,0x0,0x780000,0xfffe03f,0xffff8000,0x7,
      0x8000003c,0x3,0xc0000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1ffff,0xfe000000,0x0,0x0,0x3dffdf8,0xe1c00,0x0,0x0,0x0,0x0,0x381,
      0xc000001e,0xe070,0x7fff80,0x7c0001f3,0xe0000f9f,0x7cf8,0x3e7c0,0x1f3e00,0xfbe007,0xffc00fff,0xf007ffff,0xc03ffffe,0x1fffff0,
      0xfffff80,0x7fffe003,0xffff001f,0xfff800ff,0xffc01fff,0xc000f000,0xfc007ffe,0x3fff0,0x1fff80,0xfffc00,0x7ffe000,0x79,0xfff8001f,
      0xffe000ff,0xff0007ff,0xf8003fff,0xc0000780,0x1e0000,0xf3fff0,0x7ffe780,0x3fff3c01,0xfff9e00f,0xffcf007f,0xfe7803ff,0xf3c07ff3,
      0xff8007ff,0xe000ffff,0x7fff8,0x3fffc0,0x1fffe00,0xfffffc07,0xffffe03f,0xffff01ff,0xfff801ff,0xf8003c00,0x3c007ffe,0x3fff0,
      0x1fff80,0xfffc00,0x7ffe000,0x1d,0xfff8000f,0xff3c007f,0xf9e003ff,0xcf001ffe,0x780007c0,0x1efff8,0x1f000,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x1e,0xf000003,
      0xfe000000,0x1f000fff,0xfc00000,0x780,0xf000,0x0,0x0,0xf80000,0x0,0x7e0001e0,0x7f,0xf0000fff,0xfc03ffff,0xf81ffff0,0x780,
      0x7fff800,0x1ffe000,0x1f000000,0xfff8001f,0xff000007,0xe000003e,0x0,0x0,0x0,0x3c000,0xf800003,0xc0000783,0xfff80000,0x3ffe01ff,
      0xe00003ff,0xffe01e00,0x7ff,0xc01e0007,0x803ffff0,0x3fff800,0x3c0003c0,0x7ffffe1e,0xf078,0x7e000f,0xfe000780,0x3ff,0xc0078000,
      0x3e1fffe0,0xf000,0x7ff800,0x7e0000,0xf803e07c,0xf800,0x780003ff,0xfffc003c,0x3,0xc00001e0,0x0,0x0,0x0,0xffe3c01,0xe7ff0000,
      0x3ffc000f,0xfe78003f,0xfe00003c,0x7f0,0xf0078007,0x807ffffe,0xf000,0x78003e0,0xff00f,0x3c0f01e,0x1e001f,0xfe00079f,0xfc0007ff,
      0x3c003c00,0x7ffe0,0x1ff0,0x7fe3c0,0x7e0000,0x7c07c03e,0x3e000,0x7c0001ff,0xffe0001e,0xf0,0x780,0x0,0x0,0xfff00,0x100000,
      0x3e000000,0x7800003c,0xf800f07,0xc1e00018,0xc0,0x0,0x1f80fc,0x3fffc00,0xc0000,0x3ffff80,0x100003f,0xffffc000,0x40000002,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xff,0xfc000006,0xff87,0xfc000100,0x1ffffe,0x1e000,0x0,0x780000,0x3ffc03f,0xffff8000,0x7,
      0x8000003c,0x3,0xc0000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1ffff,0xfe000000,0x0,0x0,0x3dff9f8,0xe1c00,0x0,0x0,0x0,0x0,0x3ff,
      0xf800003c,0xfffe,0x1ffe00,0x780000f3,0xc000079e,0x3cf0,0x1e780,0xf3c00,0x7bc007,0xffc003ff,0xe007ffff,0xc03ffffe,0x1fffff0,
      0xfffff80,0x7fffe003,0xffff001f,0xfff800ff,0xffc01ffc,0xf000,0xfc001ffc,0xffe0,0x7ff00,0x3ff800,0x1ffc000,0x70,0xfff00007,
      0xff80003f,0xfc0001ff,0xe0000fff,0x780,0x1e0000,0xf3ffe0,0x1ffc780,0xffe3c00,0x7ff1e003,0xff8f001f,0xfc7800ff,0xe3c03fe1,
      0xff0003ff,0xc0007ffc,0x3ffe0,0x1fff00,0xfff800,0xfffffc07,0xffffe03f,0xffff01ff,0xfff800ff,0xf0003c00,0x3c003ffc,0x1ffe0,
      0xfff00,0x7ff800,0x3ffc000,0x38,0xfff00007,0xfe3c003f,0xf1e001ff,0x8f000ffc,0x780007c0,0x1e7ff0,0x1f000,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x30000000,
      0x1fc,0x0,0x780,0xf000,0x0,0x0,0x1f80000,0x0,0x1e0,0x1f,0xc0000000,0x0,0x1ff80,0x0,0xffc000,0x7f8000,0x0,0x3fe00007,0xfc000000,
      0x7e,0x0,0x0,0x0,0x0,0x7c00000,0x0,0x0,0xff00000,0x0,0x0,0xfe,0x0,0x0,0x3fc000,0x0,0x0,0x0,0x3,0xf8000000,0xff,0xc0000000,
      0x1ff00,0x0,0x1fe000,0x0,0x0,0x0,0x0,0x3c,0x3,0xc00001e0,0x0,0x0,0x0,0x3f80000,0x1fc0000,0x7f00003,0xf8000007,0xf0000000,
      0x0,0xf0000000,0x0,0xf000,0x0,0x0,0x0,0x7,0xf8000787,0xf00001fc,0x3c000000,0x7f80,0x0,0x1f8000,0x0,0x0,0x0,0x7c000000,0x1e,
      0xf0,0x780,0x0,0x0,0x3fc00,0x0,0x3c000000,0x7800003c,0xf000601,0xc00018,0xc0,0x0,0x0,0x3fe000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0xf,0xf0000000,0x7e03,0xf0000000,0x0,0x0,0x0,0x0,0xfe0000,0x0,0x0,0x3c,0x2007,0x80000000,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c7e0f0,0xe1c00,0x0,0x3800000,0x0,0x0,0x3ff,0xf8000078,0xfffe,0x7f800,0x0,0x0,0x0,0x0,
      0x0,0x0,0xff,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7f0,0x3f80,0x1fc00,0xfe000,0x7f0000,0x70,0x3fc00001,0xfe00000f,0xf000007f,
      0x800003fc,0x0,0x0,0xff00,0x7f0000,0x3f80000,0x1fc00000,0xfe000007,0xf000003f,0x80001f80,0xfc00007f,0xfe0,0x7f00,0x3f800,
      0x1fc000,0x0,0x0,0x0,0x3f,0xc0000000,0xff0,0x7f80,0x3fc00,0x1fe000,0xff0000,0x78,0x3fc00001,0xf800000f,0xc000007e,0x3f0,0x7c0,
      0x1e1fc0,0x1f000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x30000000,0x0,0x0,0x3c0,0x1e000,0x0,0x0,0x1f00000,0x0,0x3c0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x7c,0x0,0x0,0x0,0x0,0x3e00000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7,0xe0000000,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x3c,0x1,0xe00001e0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xf0000000,0x0,0xf000,0x0,0x0,0x0,0x0,0x780,0x0,0x3c000000,
      0x0,0x0,0x0,0x0,0x0,0x0,0x78000000,0x1e,0xf0,0x780,0x0,0x0,0x0,0x0,0x3c000000,0x78000078,0xf000000,0x18,0xc0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x180000,0x0,0x0,0x3c,0x3c0f,0x80000000,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c00000,0xe1c00,0x0,0x1800000,0x0,0x0,0x3ff,0xf80000f0,0xfffe,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0xc,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0xc,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x30,0x0,0x0,0x0,0x0,0x780,0x1e0000,0x1e000,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x30000000,
      0x0,0x0,0x3c0,0x1e000,0x0,0x0,0x1f00000,0x0,0x3c0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7c,0x0,0x0,0x0,0x0,0x1f80000,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3,0xf0000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c,0x1,0xe00001e0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1,0xe0000000,0x0,0xf000,0x0,0x0,0x0,0x0,0x780,0x0,0x3c000000,0x0,0x0,0x0,0x0,0x0,0x0,0xf8000000,
      0x1f,0xf0,0xf80,0x0,0x0,0x0,0x0,0x78000000,0xf8000078,0x1e000000,0x8,0x40,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x180000,0x0,0x0,0x3c,0x3fff,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x3c00000,0xe1c00,0x0,0x1c00000,0x0,0x0,0x1,0xc00001e0,0x70,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xe,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xe,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xf80,0x1e0000,0x3e000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x30000000,0x0,0x0,0x1e0,0x3c000,0x0,0x0,0x1f00000,
      0x0,0x780,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7c,0x0,0x0,0x0,0x0,0xfe0100,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0xf8000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3f,0xf0000000,0xf0007fe0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1,0xe0000000,
      0x0,0xf000,0x0,0x0,0x0,0x0,0x780,0x0,0x3c000000,0x0,0x0,0x0,0x0,0x0,0x0,0xf0000000,0x1f,0x800000f0,0x1f80,0x0,0x0,0x0,0x0,
      0x78000000,0xf0000070,0x1c000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x180000,0x0,0x0,0x3c,0x3ffe,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c00000,0xe1c00,0x0,0xe00000,
      0x0,0x0,0x1,0xc00003ff,0xe0000070,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0xf00,0x1e0000,0x3c000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x30000000,0x0,0x0,0x1e0,0x7c000,0x0,0x0,0x1e00000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x78,0x0,0x0,0x0,0x0,0x7fff80,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x78000000,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3f,0xf0000000,0x7fe0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x4003,0xe0000000,0x0,0x1f000,0x0,0x0,
      0x0,0x0,0x780,0x0,0x3c000000,0x0,0x0,0x0,0x0,0x0,0x1,0xf0000000,0xf,0xfc0000f0,0x3ff00,0x0,0x0,0x0,0x0,0x70000001,0xf00000e0,
      0x1c000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x180000,
      0x0,0x0,0x3c,0xff8,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c00000,0xe1c00,0x0,0xe00000,0x0,0x0,0x1,0xc00003ff,
      0xe0000070,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1f00,0x1e0000,
      0x7c000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x30000000,0x0,0x0,0xf0,0x78000,0x0,0x0,0x3e00000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xf8,0x0,
      0x0,0x0,0x0,0x1fff80,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3f,
      0xf0000000,0x7fe0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780f,0xc0000000,0x0,0x3e000,0x0,0x0,0x0,0x0,0x780,0x0,0x3c000000,0x0,
      0x0,0x0,0x0,0x0,0x3,0xe0000000,0xf,0xfc0000f0,0x3ff00,0x0,0x0,0x0,0x0,0xf0000103,0xe0000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x180000,0x0,0x0,0x3c,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c00000,0x0,0x0,0x21e00000,0x0,0x0,0x1,0xc00003ff,0xe0000070,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x10f,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x10f,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3e00,0x1e0000,0xf8000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x30000000,0x0,0x0,
      0xf8,0xf8000,0x0,0x0,0x3c00000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xf0,0x0,0x0,0x0,0x0,0x1fe00,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3f,0xf0000000,0x7fe0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x7fff,0xc0000000,0x0,0x3ffe000,0x0,0x0,0x0,0x0,0x780,0x0,0x3c000000,0x0,0x0,0x0,0x0,0x0,0x7f,0xe0000000,0x7,0xfc0000f0,
      0x3fe00,0x0,0x0,0x0,0x0,0x600001ff,0xe0000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x180000,0x0,0x0,0x3c,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c00000,0x0,0x0,
      0x3fe00000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1ff,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1ff,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x7fe00,0x1e0000,0x1ff8000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x1fffffe0,0x0,0x0,0x0,0x0,0x0,0x0,0x7fff,0x80000000,0x0,0x3ffc000,0x0,0x0,0x0,0x0,0x780,0x0,0x3c000000,0x0,
      0x0,0x0,0x0,0x0,0x7f,0xc0000000,0x0,0xfc0000f0,0x3f000,0x0,0x0,0x0,0x0,0x1ff,0xc0000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x3c00000,0x0,0x0,0x3fc00000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1fe,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1fe,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7fc00,0x1e0000,0x1ff0000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1fffffe0,0x0,0x0,0x0,0x0,0x0,0x0,0x3ffe,0x0,0x0,0x3ff8000,0x0,0x0,0x0,
      0x0,0x780,0x0,0x3c000000,0x0,0x0,0x0,0x0,0x0,0x7f,0x80000000,0x0,0xf0,0x0,0x0,0x0,0x0,0x0,0x1ff,0x80000000,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c00000,0x0,0x0,0x3f800000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1fc,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1fc,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7f800,0x1e0000,0x1fe0000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1fffffe0,0x0,0x0,0x0,0x0,0x0,0x0,0x7f8,0x0,0x0,0x3fe0000,
      0x0,0x0,0x0,0x0,0x780,0x0,0x3c000000,0x0,0x0,0x0,0x0,0x0,0x7e,0x0,0x0,0xf0,0x0,0x0,0x0,0x0,0x0,0xfe,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c00000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7e000,0x1e0000,0x1f80000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1fffffe0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xf0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xf0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0 };

    // Definition of a 40x38 'danger' color logo.
    const unsigned char logo40x38[4576] = {
      177,200,200,200,3,123,123,0,36,200,200,200,1,123,123,0,2,255,255,0,1,189,189,189,1,0,0,0,34,200,200,200,
      1,123,123,0,4,255,255,0,1,189,189,189,1,0,0,0,1,123,123,123,32,200,200,200,1,123,123,0,5,255,255,0,1,0,0,
      0,2,123,123,123,30,200,200,200,1,123,123,0,6,255,255,0,1,189,189,189,1,0,0,0,2,123,123,123,29,200,200,200,
      1,123,123,0,7,255,255,0,1,0,0,0,2,123,123,123,28,200,200,200,1,123,123,0,8,255,255,0,1,189,189,189,1,0,0,0,
      2,123,123,123,27,200,200,200,1,123,123,0,9,255,255,0,1,0,0,0,2,123,123,123,26,200,200,200,1,123,123,0,10,255,
      255,0,1,189,189,189,1,0,0,0,2,123,123,123,25,200,200,200,1,123,123,0,3,255,255,0,1,189,189,189,3,0,0,0,1,189,
      189,189,3,255,255,0,1,0,0,0,2,123,123,123,24,200,200,200,1,123,123,0,4,255,255,0,5,0,0,0,3,255,255,0,1,189,
      189,189,1,0,0,0,2,123,123,123,23,200,200,200,1,123,123,0,4,255,255,0,5,0,0,0,4,255,255,0,1,0,0,0,2,123,123,123,
      22,200,200,200,1,123,123,0,5,255,255,0,5,0,0,0,4,255,255,0,1,189,189,189,1,0,0,0,2,123,123,123,21,200,200,200,
      1,123,123,0,5,255,255,0,5,0,0,0,5,255,255,0,1,0,0,0,2,123,123,123,20,200,200,200,1,123,123,0,6,255,255,0,5,0,0,
      0,5,255,255,0,1,189,189,189,1,0,0,0,2,123,123,123,19,200,200,200,1,123,123,0,6,255,255,0,1,123,123,0,3,0,0,0,1,
      123,123,0,6,255,255,0,1,0,0,0,2,123,123,123,18,200,200,200,1,123,123,0,7,255,255,0,1,189,189,189,3,0,0,0,1,189,
      189,189,6,255,255,0,1,189,189,189,1,0,0,0,2,123,123,123,17,200,200,200,1,123,123,0,8,255,255,0,3,0,0,0,8,255,255,
      0,1,0,0,0,2,123,123,123,16,200,200,200,1,123,123,0,9,255,255,0,1,123,123,0,1,0,0,0,1,123,123,0,8,255,255,0,1,189,
      189,189,1,0,0,0,2,123,123,123,15,200,200,200,1,123,123,0,9,255,255,0,1,189,189,189,1,0,0,0,1,189,189,189,9,255,255,
      0,1,0,0,0,2,123,123,123,14,200,200,200,1,123,123,0,11,255,255,0,1,0,0,0,10,255,255,0,1,189,189,189,1,0,0,0,2,123,
      123,123,13,200,200,200,1,123,123,0,23,255,255,0,1,0,0,0,2,123,123,123,12,200,200,200,1,123,123,0,11,255,255,0,1,189,
      189,189,2,0,0,0,1,189,189,189,9,255,255,0,1,189,189,189,1,0,0,0,2,123,123,123,11,200,200,200,1,123,123,0,11,255,255,
      0,4,0,0,0,10,255,255,0,1,0,0,0,2,123,123,123,10,200,200,200,1,123,123,0,12,255,255,0,4,0,0,0,10,255,255,0,1,189,189,
      189,1,0,0,0,2,123,123,123,9,200,200,200,1,123,123,0,12,255,255,0,1,189,189,189,2,0,0,0,1,189,189,189,11,255,255,0,1,
      0,0,0,2,123,123,123,9,200,200,200,1,123,123,0,27,255,255,0,1,0,0,0,3,123,123,123,8,200,200,200,1,123,123,0,26,255,
      255,0,1,189,189,189,1,0,0,0,3,123,123,123,9,200,200,200,1,123,123,0,24,255,255,0,1,189,189,189,1,0,0,0,4,123,123,
      123,10,200,200,200,1,123,123,0,24,0,0,0,5,123,123,123,12,200,200,200,27,123,123,123,14,200,200,200,25,123,123,123,86,
      200,200,200,91,49,124,118,124,71,32,124,95,49,56,114,52,82,121,0};

    //! Display a warning message.
    /**
        \param format is a C-string describing the format of the message, as in <tt>std::printf()</tt>.
    **/
    inline void warn(const char *format, ...) {
      if (cimg::exception_mode()>=1) {
        char message[8192];
        cimg_std::va_list ap;
        va_start(ap,format);
        cimg_std::vsprintf(message,format,ap);
        va_end(ap);
#ifdef cimg_strict_warnings
        throw CImgWarningException(message);
#else
        cimg_std::fprintf(cimg_stdout,"\n%s# CImg Warning%s :\n%s\n",cimg::t_red,cimg::t_normal,message);
#endif
      }
    }

    // Execute an external system command.
    /**
       \note This function is similar to <tt>std::system()</tt>
       and is here because using the <tt>std::</tt> version on
       Windows may open undesired consoles.
     **/
    inline int system(const char *const command, const char *const module_name=0) {
#if cimg_OS==2
      PROCESS_INFORMATION pi;
      STARTUPINFO si;
      cimg_std::memset(&pi,0,sizeof(PROCESS_INFORMATION));
      cimg_std::memset(&si,0,sizeof(STARTUPINFO));
      GetStartupInfo(&si);
      si.cb = sizeof(si);
      si.wShowWindow = SW_HIDE;
      si.dwFlags |= SW_HIDE;
      const BOOL res = CreateProcess((LPCTSTR)module_name,(LPTSTR)command,0,0,FALSE,0,0,0,&si,&pi);
      if (res) {
        WaitForSingleObject(pi.hProcess, INFINITE);
        CloseHandle(pi.hThread);
        CloseHandle(pi.hProcess);
        return 0;
      } else
#endif
        return cimg_std::system(command);
      return module_name?0:1;
    }

    //! Return a reference to a temporary variable of type T.
    template<typename T>
    inline T& temporary(const T&) {
      static T temp;
      return temp;
    }

    //! Exchange values of variables \p a and \p b.
    template<typename T>
    inline void swap(T& a, T& b) { T t = a; a = b; b = t; }

    //! Exchange values of variables (\p a1,\p a2) and (\p b1,\p b2).
    template<typename T1, typename T2>
    inline void swap(T1& a1, T1& b1, T2& a2, T2& b2) {
      cimg::swap(a1,b1); cimg::swap(a2,b2);
    }

    //! Exchange values of variables (\p a1,\p a2,\p a3) and (\p b1,\p b2,\p b3).
    template<typename T1, typename T2, typename T3>
    inline void swap(T1& a1, T1& b1, T2& a2, T2& b2, T3& a3, T3& b3) {
      cimg::swap(a1,b1,a2,b2); cimg::swap(a3,b3);
    }

    //! Exchange values of variables (\p a1,\p a2,...,\p a4) and (\p b1,\p b2,...,\p b4).
    template<typename T1, typename T2, typename T3, typename T4>
    inline void swap(T1& a1, T1& b1, T2& a2, T2& b2, T3& a3, T3& b3, T4& a4, T4& b4) {
      cimg::swap(a1,b1,a2,b2,a3,b3); cimg::swap(a4,b4);
    }

    //! Exchange values of variables (\p a1,\p a2,...,\p a5) and (\p b1,\p b2,...,\p b5).
    template<typename T1, typename T2, typename T3, typename T4, typename T5>
    inline void swap(T1& a1, T1& b1, T2& a2, T2& b2, T3& a3, T3& b3, T4& a4, T4& b4, T5& a5, T5& b5) {
      cimg::swap(a1,b1,a2,b2,a3,b3,a4,b4); cimg::swap(a5,b5);
    }

    //! Exchange values of variables (\p a1,\p a2,...,\p a6) and (\p b1,\p b2,...,\p b6).
    template<typename T1, typename T2, typename T3, typename T4, typename T5, typename T6>
    inline void swap(T1& a1, T1& b1, T2& a2, T2& b2, T3& a3, T3& b3, T4& a4, T4& b4, T5& a5, T5& b5, T6& a6, T6& b6) {
      cimg::swap(a1,b1,a2,b2,a3,b3,a4,b4,a5,b5); cimg::swap(a6,b6);
    }

    //! Exchange values of variables (\p a1,\p a2,...,\p a7) and (\p b1,\p b2,...,\p b7).
    template<typename T1, typename T2, typename T3, typename T4, typename T5, typename T6, typename T7>
    inline void swap(T1& a1, T1& b1, T2& a2, T2& b2, T3& a3, T3& b3, T4& a4, T4& b4, T5& a5, T5& b5, T6& a6, T6& b6,
                     T7& a7, T7& b7) {
      cimg::swap(a1,b1,a2,b2,a3,b3,a4,b4,a5,b5,a6,b6); cimg::swap(a7,b7);
    }

    //! Exchange values of variables (\p a1,\p a2,...,\p a8) and (\p b1,\p b2,...,\p b8).
    template<typename T1, typename T2, typename T3, typename T4, typename T5, typename T6, typename T7, typename T8>
    inline void swap(T1& a1, T1& b1, T2& a2, T2& b2, T3& a3, T3& b3, T4& a4, T4& b4, T5& a5, T5& b5, T6& a6, T6& b6,
                     T7& a7, T7& b7, T8& a8, T8& b8) {
      cimg::swap(a1,b1,a2,b2,a3,b3,a4,b4,a5,b5,a6,b6,a7,b7); cimg::swap(a8,b8);
    }

    //! Return the current endianness of the CPU.
    /**
       \return \c false for "Little Endian", \c true for "Big Endian".
    **/
    inline bool endianness() {
      const int x = 1;
      return ((unsigned char*)&x)[0]?false:true;
    }

    //! Invert endianness of a memory buffer.
    template<typename T>
    inline void invert_endianness(T* const buffer, const unsigned int size) {
      if (size) switch (sizeof(T)) {
      case 1 : break;
      case 2 : { for (unsigned short *ptr = (unsigned short*)buffer+size; ptr>(unsigned short*)buffer; ) {
        const unsigned short val = *(--ptr);
        *ptr = (unsigned short)((val>>8)|((val<<8)));
      }} break;
      case 4 : { for (unsigned int *ptr = (unsigned int*)buffer+size; ptr>(unsigned int*)buffer; ) {
        const unsigned int val = *(--ptr);
        *ptr = (val>>24)|((val>>8)&0xff00)|((val<<8)&0xff0000)|(val<<24);
      }} break;
      default : { for (T* ptr = buffer+size; ptr>buffer; ) {
        unsigned char *pb = (unsigned char*)(--ptr), *pe = pb + sizeof(T);
        for (int i=0; i<(int)sizeof(T)/2; ++i) swap(*(pb++),*(--pe));
      }}
      }
    }

    //! Invert endianness of a single variable.
    template<typename T>
    inline T& invert_endianness(T& a) {
      invert_endianness(&a,1);
      return a;
    }

    //! Get the value of a system timer with a millisecond precision.
    inline unsigned long time() {
#if cimg_OS==1
      struct timeval st_time;
      gettimeofday(&st_time,0);
      return (unsigned long)(st_time.tv_usec/1000 + st_time.tv_sec*1000);
#elif cimg_OS==2
      static SYSTEMTIME st_time;
      GetSystemTime(&st_time);
      return (unsigned long)(st_time.wMilliseconds + 1000*(st_time.wSecond + 60*(st_time.wMinute + 60*st_time.wHour)));
#else
      return 0;
#endif
    }

    //! Sleep for a certain numbers of milliseconds.
    /**
       This function frees the CPU ressources during the sleeping time.
       It may be used to temporize your program properly, without wasting CPU time.
    **/
    inline void sleep(const unsigned int milliseconds) {
#if cimg_OS==1
      struct timespec tv;
      tv.tv_sec = milliseconds/1000;
      tv.tv_nsec = (milliseconds%1000)*1000000;
      nanosleep(&tv,0);
#elif cimg_OS==2
      Sleep(milliseconds);
#endif
    }

    inline unsigned int _sleep(const unsigned int milliseconds, unsigned long& timer) {
      if (!timer) timer = cimg::time();
      const unsigned long current_time = cimg::time();
      if (current_time>=timer+milliseconds) { timer = current_time; return 0; }
      const unsigned long time_diff = timer + milliseconds - current_time;
      timer = current_time + time_diff;
      cimg::sleep(time_diff);
      return (unsigned int)time_diff;
    }

    //! Wait for a certain number of milliseconds since the last call.
    /**
       This function is equivalent to sleep() but the waiting time is computed with regard to the last call
       of wait(). It may be used to temporize your program properly.
    **/
    inline unsigned int wait(const unsigned int milliseconds) {
      static unsigned long timer = 0;
      if (!timer) timer = cimg::time();
      return _sleep(milliseconds,timer);
    }

    // Use a specific srand initialization to avoid multi-threads to have to the
    // same series of random numbers (executed only once for a single program).
    inline void srand() {
      static bool first_time = true;
      if (first_time) {
        cimg_std::srand(cimg::time());
        unsigned char *const rand_ptr = new unsigned char[1+cimg_std::rand()%2048];
        cimg_std::srand((unsigned int)cimg_std::rand() + *(unsigned int*)(void*)rand_ptr);
        delete[] rand_ptr;
        first_time = false;
      }
    }

    //! Return a left bitwise-rotated number.
    template<typename T>
    inline const T rol(const T a, const unsigned int n=1) {
      return n?(T)((a<<n)|(a>>((sizeof(T)<<3)-n))):a;
    }

    //! Return a right bitwise-rotated number.
    template<typename T>
    inline const T ror(const T a, const unsigned int n=1) {
      return n?(T)((a>>n)|(a<<((sizeof(T)<<3)-n))):a;
    }

    //! Return the absolute value of a number.
    /**
       \note This function is different from <tt>std::abs()</tt> or <tt>std::fabs()</tt>
       because it is able to consider a variable of any type, without cast needed.
    **/
    template<typename T>
    inline T abs(const T a) {
      return a>=0?a:-a;
    }
    inline bool abs(const bool a) {
      return a;
    }
    inline unsigned char abs(const unsigned char a) {
      return a;
    }
    inline unsigned short abs(const unsigned short a) {
      return a;
    }
    inline unsigned int abs(const unsigned int a) {
      return a;
    }
    inline unsigned long abs(const unsigned long a) {
      return a;
    }
    inline double abs(const double a) {
      return cimg_std::fabs(a);
    }
    inline float abs(const float a) {
      return (float)cimg_std::fabs((double)a);
    }
    inline int abs(const int a) {
      return cimg_std::abs(a);
    }

    //! Return the square of a number.
    template<typename T>
    inline T sqr(const T val) {
      return val*val;
    }

    //! Return 1 + log_10(x).
    inline int xln(const int x) {
      return x>0?(int)(1+cimg_std::log10((double)x)):1;
    }

    //! Return the minimum value between two numbers.
    template<typename t1, typename t2>
    inline typename cimg::superset<t1,t2>::type min(const t1& a, const t2& b) {
      typedef typename cimg::superset<t1,t2>::type t1t2;
      return (t1t2)(a<=b?a:b);
    }

    //! Return the minimum value between three numbers.
    template<typename t1, typename t2, typename t3>
    inline typename cimg::superset2<t1,t2,t3>::type min(const t1& a, const t2& b, const t3& c) {
      typedef typename cimg::superset2<t1,t2,t3>::type t1t2t3;
      return (t1t2t3)cimg::min(cimg::min(a,b),c);
    }

    //! Return the minimum value between four numbers.
    template<typename t1, typename t2, typename t3, typename t4>
    inline typename cimg::superset3<t1,t2,t3,t4>::type min(const t1& a, const t2& b, const t3& c, const t4& d) {
      typedef typename cimg::superset3<t1,t2,t3,t4>::type t1t2t3t4;
      return (t1t2t3t4)cimg::min(cimg::min(a,b,c),d);
    }

    //! Return the maximum value between two numbers.
    template<typename t1, typename t2>
    inline typename cimg::superset<t1,t2>::type max(const t1& a, const t2& b) {
      typedef typename cimg::superset<t1,t2>::type t1t2;
      return (t1t2)(a>=b?a:b);
    }

    //! Return the maximum value between three numbers.
    template<typename t1, typename t2, typename t3>
    inline typename cimg::superset2<t1,t2,t3>::type max(const t1& a, const t2& b, const t3& c) {
      typedef typename cimg::superset2<t1,t2,t3>::type t1t2t3;
      return (t1t2t3)cimg::max(cimg::max(a,b),c);
    }

    //! Return the maximum value between four numbers.
    template<typename t1, typename t2, typename t3, typename t4>
    inline typename cimg::superset3<t1,t2,t3,t4>::type max(const t1& a, const t2& b, const t3& c, const t4& d) {
      typedef typename cimg::superset3<t1,t2,t3,t4>::type t1t2t3t4;
      return (t1t2t3t4)cimg::max(cimg::max(a,b,c),d);
    }

    //! Return the sign of a number.
    template<typename T>
    inline T sign(const T x) {
      return (x<0)?(T)(-1):(x==0?(T)0:(T)1);
    }

    //! Return the nearest power of 2 higher than a given number.
    template<typename T>
    inline unsigned long nearest_pow2(const T x) {
      unsigned long i = 1;
      while (x>i) i<<=1;
      return i;
    }

    //! Return the modulo of a number.
    /**
       \note This modulo function accepts negative and floating-points modulo numbers, as well as
       variable of any type.
    **/
    template<typename T>
    inline T mod(const T& x, const T& m) {
      const double dx = (double)x, dm = (double)m;
      if (x<0) { return (T)(dm+dx+dm*cimg_std::floor(-dx/dm)); }
      return (T)(dx-dm*cimg_std::floor(dx/dm));
    }
    inline int mod(const bool x, const bool m) {
      return m?(x?1:0):0;
    }
    inline int mod(const char x, const char m) {
      return x>=0?x%m:(x%m?m+x%m:0);
    }
    inline int mod(const short x, const short m) {
      return x>=0?x%m:(x%m?m+x%m:0);
    }
    inline int mod(const int x, const int m) {
      return x>=0?x%m:(x%m?m+x%m:0);
    }
    inline int mod(const long x, const long m) {
      return x>=0?x%m:(x%m?m+x%m:0);
    }
    inline int mod(const unsigned char x, const unsigned char m) {
      return x%m;
    }
    inline int mod(const unsigned short x, const unsigned short m) {
      return x%m;
    }
    inline int mod(const unsigned int x, const unsigned int m) {
      return x%m;
    }
    inline int mod(const unsigned long x, const unsigned long m) {
      return x%m;
    }

    //! Return the minmod of two numbers.
    /**
       <i>minmod(\p a,\p b)</i> is defined to be :
       - <i>minmod(\p a,\p b) = min(\p a,\p b)</i>, if \p a and \p b have the same sign.
       - <i>minmod(\p a,\p b) = 0</i>, if \p a and \p b have different signs.
    **/
    template<typename T>
    inline T minmod(const T a, const T b) {
      return a*b<=0?0:(a>0?(a<b?a:b):(a<b?b:a));
    }

    //! Return a random variable between [0,1] with respect to an uniform distribution.
    inline double rand() {
      static bool first_time = true;
      if (first_time) { cimg::srand(); first_time = false; }
      return (double)cimg_std::rand()/RAND_MAX;
    }

    //! Return a random variable between [-1,1] with respect to an uniform distribution.
    inline double crand() {
      return 1-2*cimg::rand();
    }

    //! Return a random variable following a gaussian distribution and a standard deviation of 1.
    inline double grand() {
      double x1, w;
      do {
        const double x2 = 2*cimg::rand() - 1.0;
        x1 = 2*cimg::rand()-1.0;
        w = x1*x1 + x2*x2;
      } while (w<=0 || w>=1.0);
      return x1*cimg_std::sqrt((-2*cimg_std::log(w))/w);
    }

    //! Return a random variable following a Poisson distribution of parameter z.
    inline unsigned int prand(const double z) {
      if (z<=1.0e-10) return 0;
      if (z>100.0) return (unsigned int)((std::sqrt(z) * cimg::grand()) + z);
      unsigned int k = 0;
      const double y = std::exp(-z);
      for (double s = 1.0; s>=y; ++k) s*=cimg::rand();
      return k-1;
    }

    //! Return a rounded number.
    /**
       \param x is the number to be rounded.
       \param y is the rounding precision.
       \param rounding_type defines the type of rounding (0=nearest, -1=backward, 1=forward).
    **/
    inline double round(const double x, const double y, const int rounding_type=0) {
      if (y<=0) return x;
      const double delta = cimg::mod(x,y);
      if (delta==0.0) return x;
      const double
        backward = x - delta,
        forward = backward + y;
      return rounding_type<0?backward:(rounding_type>0?forward:(2*delta<y?backward:forward));
    }

    inline double _pythagore(double a, double b) {
      const double absa = cimg::abs(a), absb = cimg::abs(b);
      if (absa>absb) { const double tmp = absb/absa; return absa*cimg_std::sqrt(1.0+tmp*tmp); }
      else { const double tmp = absa/absb; return (absb==0?0:absb*cimg_std::sqrt(1.0+tmp*tmp)); }
    }

    //! Remove the 'case' of an ASCII character.
    inline char uncase(const char x) {
      return (char)((x<'A'||x>'Z')?x:x-'A'+'a');
    }

    //! Remove the 'case' of a C string.
    /**
       Acts in-place.
    **/
    inline void uncase(char *const string) {
      if (string) for (char *ptr = string; *ptr; ++ptr) *ptr = uncase(*ptr);
    }

    //! Read a float number from a C-string.
    /**
       \note This function is quite similar to <tt>std::atof()</tt>,
       but that it allows the retrieval of fractions as in "1/2".
    **/
    inline float atof(const char *const str) {
      float x = 0,y = 1;
      if (!str) return 0; else { cimg_std::sscanf(str,"%g/%g",&x,&y); return x/y; }
    }

    //! Compute the length of a C-string.
    /**
       \note This function is similar to <tt>std::strlen()</tt>
       and is here because some old compilers do not
       define the <tt>std::</tt> version.
    **/
    inline int strlen(const char *const s) {
      if (!s) return -1;
      int k = 0;
      for (const char *ns = s; *ns; ++ns) ++k;
      return k;
    }

    //! Compare the first \p n characters of two C-strings.
    /**
       \note This function is similar to <tt>std::strncmp()</tt>
       and is here because some old compilers do not
       define the <tt>std::</tt> version.
    **/
    inline int strncmp(const char *const s1, const char *const s2, const int l) {
      if (!s1) return s2?-1:0;
      const char *ns1 = s1, *ns2 = s2;
      int k, diff = 0; for (k = 0; k<l && !(diff = *ns1-*ns2); ++k) { ++ns1; ++ns2; }
      return k!=l?diff:0;
    }

    //! Compare the first \p n characters of two C-strings, ignoring the case.
    /**
       \note This function is similar to <tt>std::strncasecmp()</tt>
       and is here because some old compilers do not
       define the <tt>std::</tt> version.
    **/
    inline int strncasecmp(const char *const s1, const char *const s2, const int l) {
      if (!s1) return s2?-1:0;
      const char *ns1 = s1, *ns2 = s2;
      int k, diff = 0; for (k = 0; k<l && !(diff = uncase(*ns1)-uncase(*ns2)); ++k) { ++ns1; ++ns2; }
      return k!=l?diff:0;
    }

    //! Compare two C-strings.
    /**
       \note This function is similar to <tt>std::strcmp()</tt>
       and is here because some old compilers do not
       define the <tt>std::</tt> version.
    **/
    inline int strcmp(const char *const s1, const char *const s2) {
      const int l1 = cimg::strlen(s1), l2 = cimg::strlen(s2);
      return cimg::strncmp(s1,s2,1+(l1<l2?l1:l2));
    }

    //! Compare two C-strings, ignoring the case.
    /**
       \note This function is similar to <tt>std::strcasecmp()</tt>
       and is here because some old compilers do not
       define the <tt>std::</tt> version.
    **/
    inline int strcasecmp(const char *const s1, const char *const s2) {
      const int l1 = cimg::strlen(s1), l2 = cimg::strlen(s2);
      return cimg::strncasecmp(s1,s2,1+(l1<l2?l1:l2));
    }

    //! Find a character in a C-string.
    inline int strfind(const char *const s, const char c) {
      if (!s) return -1;
      int l; for (l = cimg::strlen(s); l>=0 && s[l]!=c; --l) {}
      return l;
    }

    //! Remove useless delimiters on the borders of a C-string
    inline bool strpare(char *const s, const char delimiter=' ', const bool symmetric=false) {
      if (!s) return false;
      const int l = cimg::strlen(s);
      int p, q;
      if (symmetric) for (p = 0, q = l-1; p<q && s[p]==delimiter && s[q]==delimiter; ++p) --q;
      else {
        for (p = 0; p<l && s[p]==delimiter; ) ++p;
        for (q = l-1; q>p && s[q]==delimiter; ) --q;
      }
      const int n = q - p + 1;
      if (n!=l) { cimg_std::memmove(s,s+p,n); s[n] = '\0'; return true; }
      return false;
    }

    //! Remove useless spaces and symmetric delimiters ', " and ` from a C-string.
    inline void strclean(char *const s) {
      if (!s) return;
      strpare(s,' ',false);
      for (bool need_iter = true; need_iter; ) {
        need_iter = false;
        need_iter |= strpare(s,'\'',true);
        need_iter |= strpare(s,'\"',true);
        need_iter |= strpare(s,'`',true);
      }
    }

    //! Replace explicit escape sequences '\x' in C-strings (where x in [ntvbrfa?'"0]).
    inline void strescape(char *const s) {
#define cimg_strescape(ci,co) case ci: *nd = co; break;
      char *ns, *nd;
      for (ns = nd = s; *ns; ++ns, ++nd)
        if (*ns=='\\') switch (*(++ns)) {
            cimg_strescape('n','\n');
            cimg_strescape('t','\t');
            cimg_strescape('v','\v');
            cimg_strescape('b','\b');
            cimg_strescape('r','\r');
            cimg_strescape('f','\f');
            cimg_strescape('a','\a');
            cimg_strescape('\\','\\');
            cimg_strescape('\?','\?');
            cimg_strescape('\'','\'');
            cimg_strescape('\"','\"');
            cimg_strescape('\0','\0');
          }
        else *nd = *ns;
      *nd = 0;
    }

    //! Compute the basename of a filename.
    inline const char* basename(const char *const s)  {
      return (cimg_OS!=2)?(s?s+1+cimg::strfind(s,'/'):0):(s?s+1+cimg::strfind(s,'\\'):0);
    }

    // Generate a random filename.
    inline const char* filenamerand() {
      static char id[9] = { 0,0,0,0,0,0,0,0,0 };
      cimg::srand();
      for (unsigned int k=0; k<8; ++k) {
        const int v = (int)cimg_std::rand()%3;
        id[k] = (char)(v==0?('0'+(cimg_std::rand()%10)):(v==1?('a'+(cimg_std::rand()%26)):('A'+(cimg_std::rand()%26))));
      }
      return id;
    }

    // Convert filename into a Windows-style filename.
    inline void winformat_string(char *const s) {
      if (s && s[0]) {
#if cimg_OS==2
        char *const ns = new char[MAX_PATH];
        if (GetShortPathNameA(s,ns,MAX_PATH)) cimg_std::strcpy(s,ns);
#endif
      }
    }

    //! Return or set path to store temporary files.
    inline const char* temporary_path(const char *const user_path=0, const bool reinit_path=false) {
#define _cimg_test_temporary_path(p) \
      if (!path_found) { \
        cimg_std::sprintf(st_path,"%s",p); \
        cimg_std::sprintf(tmp,"%s%s%s",st_path,cimg_OS==2?"\\":"/",filetmp); \
        if ((file=cimg_std::fopen(tmp,"wb"))!=0) { cimg_std::fclose(file); cimg_std::remove(tmp); path_found = true; } \
      }
      static char *st_path = 0;
      if (reinit_path && st_path) { delete[] st_path; st_path = 0; }
      if (user_path) {
        if (!st_path) st_path = new char[1024];
        cimg_std::memset(st_path,0,1024);
        cimg_std::strncpy(st_path,user_path,1023);
      } else if (!st_path) {
        st_path = new char[1024];
        cimg_std::memset(st_path,0,1024);
        bool path_found = false;
        char tmp[1024], filetmp[512];
        cimg_std::FILE *file = 0;
        cimg_std::sprintf(filetmp,"%s.tmp",cimg::filenamerand());
        char *tmpPath = getenv("TMP");
        if (!tmpPath) { tmpPath = getenv("TEMP"); winformat_string(tmpPath); }
        if (tmpPath) _cimg_test_temporary_path(tmpPath);
#if cimg_OS==2
        _cimg_test_temporary_path("C:\\WINNT\\Temp");
        _cimg_test_temporary_path("C:\\WINDOWS\\Temp");
        _cimg_test_temporary_path("C:\\Temp");
        _cimg_test_temporary_path("C:");
        _cimg_test_temporary_path("D:\\WINNT\\Temp");
        _cimg_test_temporary_path("D:\\WINDOWS\\Temp");
        _cimg_test_temporary_path("D:\\Temp");
        _cimg_test_temporary_path("D:");
#else
        _cimg_test_temporary_path("/tmp");
        _cimg_test_temporary_path("/var/tmp");
#endif
        if (!path_found) {
          st_path[0]='\0';
          cimg_std::strcpy(tmp,filetmp);
          if ((file=cimg_std::fopen(tmp,"wb"))!=0) { cimg_std::fclose(file); cimg_std::remove(tmp); path_found = true; }
        }
        if (!path_found)
          throw CImgIOException("cimg::temporary_path() : Unable to find a temporary path accessible for writing\n"
                                "you have to set the macro 'cimg_temporary_path' to a valid path where you have writing access :\n"
                                "#define cimg_temporary_path \"path\" (before including 'CImg.h')");
      }
      return st_path;
    }

    // Return or set path to the "Program files/" directory (windows only).
#if cimg_OS==2
    inline const char* programfiles_path(const char *const user_path=0, const bool reinit_path=false) {
      static char *st_path = 0;
      if (reinit_path && st_path) { delete[] st_path; st_path = 0; }
      if (user_path) {
        if (!st_path) st_path = new char[1024];
        cimg_std::memset(st_path,0,1024);
        cimg_std::strncpy(st_path,user_path,1023);
      } else if (!st_path) {
        st_path = new char[MAX_PATH];
        cimg_std::memset(st_path,0,MAX_PATH);
        // Note : in the following line, 0x26 = CSIDL_PROGRAM_FILES (not defined on every compiler).
#if !defined(__INTEL_COMPILER)
        if (!SHGetSpecialFolderPathA(0,st_path,0x0026,false)) {
          const char *pfPath = getenv("PROGRAMFILES");
          if (pfPath) cimg_std::strncpy(st_path,pfPath,MAX_PATH-1);
          else cimg_std::strcpy(st_path,"C:\\PROGRA~1");
        }
#else
        cimg_std::strcpy(st_path,"C:\\PROGRA~1");
#endif
      }
      return st_path;
    }
#endif

    //! Return or set path to the ImageMagick's \c convert tool.
    inline const char* imagemagick_path(const char *const user_path=0, const bool reinit_path=false) {
      static char *st_path = 0;
      if (reinit_path && st_path) { delete[] st_path; st_path = 0; }
      if (user_path) {
        if (!st_path) st_path = new char[1024];
        cimg_std::memset(st_path,0,1024);
        cimg_std::strncpy(st_path,user_path,1023);
      } else if (!st_path) {
        st_path = new char[1024];
        cimg_std::memset(st_path,0,1024);
        bool path_found = false;
        cimg_std::FILE *file = 0;
#if cimg_OS==2
        const char *pf_path = programfiles_path();
        if (!path_found) {
          cimg_std::sprintf(st_path,".\\convert.exe");
          if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
        }
        { for (int k=32; k>=10 && !path_found; --k) {
          cimg_std::sprintf(st_path,"%s\\IMAGEM~1.%.2d-\\convert.exe",pf_path,k);
          if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
        }}
        { for (int k=9; k>=0 && !path_found; --k) {
          cimg_std::sprintf(st_path,"%s\\IMAGEM~1.%d-Q\\convert.exe",pf_path,k);
          if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
        }}
        { for (int k=32; k>=0 && !path_found; --k) {
          cimg_std::sprintf(st_path,"%s\\IMAGEM~1.%d\\convert.exe",pf_path,k);
          if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
        }}
        { for (int k=32; k>=10 && !path_found; --k) {
          cimg_std::sprintf(st_path,"%s\\IMAGEM~1.%.2d-\\VISUA~1\\BIN\\convert.exe",pf_path,k);
          if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
        }}
        { for (int k=9; k>=0 && !path_found; --k) {
          cimg_std::sprintf(st_path,"%s\\IMAGEM~1.%d-Q\\VISUA~1\\BIN\\convert.exe",pf_path,k);
          if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
        }}
        { for (int k=32; k>=0 && !path_found; --k) {
          cimg_std::sprintf(st_path,"%s\\IMAGEM~1.%d\\VISUA~1\\BIN\\convert.exe",pf_path,k);
          if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
        }}
        { for (int k=32; k>=10 && !path_found; --k) {
          cimg_std::sprintf(st_path,"C:\\IMAGEM~1.%.2d-\\convert.exe",k);
          if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
        }}
        { for (int k=9; k>=0 && !path_found; --k) {
          cimg_std::sprintf(st_path,"C:\\IMAGEM~1.%d-Q\\convert.exe",k);
          if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
        }}
        { for (int k=32; k>=0 && !path_found; --k) {
          cimg_std::sprintf(st_path,"C:\\IMAGEM~1.%d\\convert.exe",k);
          if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
        }}
        { for (int k=32; k>=10 && !path_found; --k) {
          cimg_std::sprintf(st_path,"C:\\IMAGEM~1.%.2d-\\VISUA~1\\BIN\\convert.exe",k);
          if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
        }}
        { for (int k=9; k>=0 && !path_found; --k) {
          cimg_std::sprintf(st_path,"C:\\IMAGEM~1.%d-Q\\VISUA~1\\BIN\\convert.exe",k);
          if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
        }}
        { for (int k=32; k>=0 && !path_found; --k) {
          cimg_std::sprintf(st_path,"C:\\IMAGEM~1.%d\\VISUA~1\\BIN\\convert.exe",k);
          if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
        }}
        { for (int k=32; k>=10 && !path_found; --k) {
          cimg_std::sprintf(st_path,"D:\\IMAGEM~1.%.2d-\\convert.exe",k);
          if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
        }}
        { for (int k=9; k>=0 && !path_found; --k) {
          cimg_std::sprintf(st_path,"D:\\IMAGEM~1.%d-Q\\convert.exe",k);
          if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
        }}
        { for (int k=32; k>=0 && !path_found; --k) {
          cimg_std::sprintf(st_path,"D:\\IMAGEM~1.%d\\convert.exe",k);
          if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
        }}
        { for (int k=32; k>=10 && !path_found; --k) {
          cimg_std::sprintf(st_path,"D:\\IMAGEM~1.%.2d-\\VISUA~1\\BIN\\convert.exe",k);
          if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
        }}
        { for (int k=9; k>=0 && !path_found; --k) {
          cimg_std::sprintf(st_path,"D:\\IMAGEM~1.%d-Q\\VISUA~1\\BIN\\convert.exe",k);
          if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
        }}
        { for (int k=32; k>=0 && !path_found; --k) {
          cimg_std::sprintf(st_path,"D:\\IMAGEM~1.%d\\VISUA~1\\BIN\\convert.exe",k);
          if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
        }}
        if (!path_found) cimg_std::strcpy(st_path,"convert.exe");
#else
        if (!path_found) {
          cimg_std::sprintf(st_path,"./convert");
          if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
        }
        if (!path_found) cimg_std::strcpy(st_path,"convert");
#endif
        winformat_string(st_path);
      }
      return st_path;
    }

    //! Return path of the GraphicsMagick's \c gm tool.
    inline const char* graphicsmagick_path(const char *const user_path=0, const bool reinit_path=false) {
      static char *st_path = 0;
      if (reinit_path && st_path) { delete[] st_path; st_path = 0; }
      if (user_path) {
        if (!st_path) st_path = new char[1024];
        cimg_std::memset(st_path,0,1024);
        cimg_std::strncpy(st_path,user_path,1023);
      } else if (!st_path) {
        st_path = new char[1024];
        cimg_std::memset(st_path,0,1024);
        bool path_found = false;
        cimg_std::FILE *file = 0;
#if cimg_OS==2
        const char* pf_path = programfiles_path();
        if (!path_found) {
          cimg_std::sprintf(st_path,".\\gm.exe");
          if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
        }
        { for (int k=32; k>=10 && !path_found; --k) {
          cimg_std::sprintf(st_path,"%s\\GRAPHI~1.%.2d-\\gm.exe",pf_path,k);
          if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
        }}
        { for (int k=9; k>=0 && !path_found; --k) {
          cimg_std::sprintf(st_path,"%s\\GRAPHI~1.%d-Q\\gm.exe",pf_path,k);
          if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
        }}
        { for (int k=32; k>=0 && !path_found; --k) {
          cimg_std::sprintf(st_path,"%s\\GRAPHI~1.%d\\gm.exe",pf_path,k);
          if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
        }}
        { for (int k=32; k>=10 && !path_found; --k) {
          cimg_std::sprintf(st_path,"%s\\GRAPHI~1.%.2d-\\VISUA~1\\BIN\\gm.exe",pf_path,k);
          if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
        }}
        { for (int k=9; k>=0 && !path_found; --k) {
          cimg_std::sprintf(st_path,"%s\\GRAPHI~1.%d-Q\\VISUA~1\\BIN\\gm.exe",pf_path,k);
          if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
        }}
        { for (int k=32; k>=0 && !path_found; --k) {
          cimg_std::sprintf(st_path,"%s\\GRAPHI~1.%d\\VISUA~1\\BIN\\gm.exe",pf_path,k);
          if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
        }}
        { for (int k=32; k>=10 && !path_found; --k) {
          cimg_std::sprintf(st_path,"C:\\GRAPHI~1.%.2d-\\gm.exe",k);
          if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
        }}
        { for (int k=9; k>=0 && !path_found; --k) {
          cimg_std::sprintf(st_path,"C:\\GRAPHI~1.%d-Q\\gm.exe",k);
          if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
        }}
        { for (int k=32; k>=0 && !path_found; --k) {
          cimg_std::sprintf(st_path,"C:\\GRAPHI~1.%d\\gm.exe",k);
          if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
        }}
        { for (int k=32; k>=10 && !path_found; --k) {
          cimg_std::sprintf(st_path,"C:\\GRAPHI~1.%.2d-\\VISUA~1\\BIN\\gm.exe",k);
          if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
        }}
        { for (int k=9; k>=0 && !path_found; --k) {
          cimg_std::sprintf(st_path,"C:\\GRAPHI~1.%d-Q\\VISUA~1\\BIN\\gm.exe",k);
          if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
        }}
        { for (int k=32; k>=0 && !path_found; --k) {
          cimg_std::sprintf(st_path,"C:\\GRAPHI~1.%d\\VISUA~1\\BIN\\gm.exe",k);
          if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
        }}
        { for (int k=32; k>=10 && !path_found; --k) {
          cimg_std::sprintf(st_path,"D:\\GRAPHI~1.%.2d-\\gm.exe",k);
          if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
        }}
        { for (int k=9; k>=0 && !path_found; --k) {
          cimg_std::sprintf(st_path,"D:\\GRAPHI~1.%d-Q\\gm.exe",k);
          if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
        }}
        { for (int k=32; k>=0 && !path_found; --k) {
          cimg_std::sprintf(st_path,"D:\\GRAPHI~1.%d\\gm.exe",k);
          if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
        }}
        { for (int k=32; k>=10 && !path_found; --k) {
          cimg_std::sprintf(st_path,"D:\\GRAPHI~1.%.2d-\\VISUA~1\\BIN\\gm.exe",k);
          if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
        }}
        { for (int k=9; k>=0 && !path_found; --k) {
          cimg_std::sprintf(st_path,"D:\\GRAPHI~1.%d-Q\\VISUA~1\\BIN\\gm.exe",k);
          if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
        }}
        { for (int k=32; k>=0 && !path_found; --k) {
          cimg_std::sprintf(st_path,"D:\\GRAPHI~1.%d\\VISUA~1\\BIN\\gm.exe",k);
          if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
        }}
        if (!path_found) cimg_std::strcpy(st_path,"gm.exe");
#else
        if (!path_found) {
          cimg_std::sprintf(st_path,"./gm");
          if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
        }
        if (!path_found) cimg_std::strcpy(st_path,"gm");
#endif
        winformat_string(st_path);
      }
      return st_path;
    }

    //! Return or set path of the \c XMedcon tool.
    inline const char* medcon_path(const char *const user_path=0, const bool reinit_path=false) {
      static char *st_path = 0;
      if (reinit_path && st_path) { delete[] st_path; st_path = 0; }
      if (user_path) {
        if (!st_path) st_path = new char[1024];
        cimg_std::memset(st_path,0,1024);
        cimg_std::strncpy(st_path,user_path,1023);
      } else if (!st_path) {
        st_path = new char[1024];
        cimg_std::memset(st_path,0,1024);
        bool path_found = false;
        cimg_std::FILE *file = 0;
#if cimg_OS==2
        const char* pf_path = programfiles_path();
        if (!path_found) {
          cimg_std::sprintf(st_path,".\\medcon.bat");
          if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
        }
        if (!path_found) {
          cimg_std::sprintf(st_path,".\\medcon.exe");
          if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
        }
        if (!path_found) {
          cimg_std::sprintf(st_path,"%s\\XMedCon\\bin\\medcon.bat",pf_path);
          if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
        }
        if (!path_found) {
          cimg_std::sprintf(st_path,"%s\\XMedCon\\bin\\medcon.exe",pf_path);
          if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
        }
        if (!path_found) cimg_std::strcpy(st_path,"medcon.bat");
#else
        if (!path_found) {
          cimg_std::sprintf(st_path,"./medcon");
          if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
        }
        if (!path_found) cimg_std::strcpy(st_path,"medcon");
#endif
        winformat_string(st_path);
      }
      return st_path;
    }

    //! Return or set path to the 'ffmpeg' command.
    inline const char *ffmpeg_path(const char *const user_path=0, const bool reinit_path=false) {
      static char *st_path = 0;
      if (reinit_path && st_path) { delete[] st_path; st_path = 0; }
      if (user_path) {
        if (!st_path) st_path = new char[1024];
        cimg_std::memset(st_path,0,1024);
        cimg_std::strncpy(st_path,user_path,1023);
      } else if (!st_path) {
        st_path = new char[1024];
        cimg_std::memset(st_path,0,1024);
        bool path_found = false;
        cimg_std::FILE *file = 0;
#if cimg_OS==2
        if (!path_found) {
          cimg_std::sprintf(st_path,".\\ffmpeg.exe");
          if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
        }
        if (!path_found) cimg_std::strcpy(st_path,"ffmpeg.exe");
#else
        if (!path_found) {
          cimg_std::sprintf(st_path,"./ffmpeg");
          if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
        }
        if (!path_found) cimg_std::strcpy(st_path,"ffmpeg");
#endif
        winformat_string(st_path);
      }
      return st_path;
    }

    //! Return or set path to the 'gzip' command.
    inline const char *gzip_path(const char *const user_path=0, const bool reinit_path=false) {
      static char *st_path = 0;
      if (reinit_path && st_path) { delete[] st_path; st_path = 0; }
      if (user_path) {
        if (!st_path) st_path = new char[1024];
        cimg_std::memset(st_path,0,1024);
        cimg_std::strncpy(st_path,user_path,1023);
      } else if (!st_path) {
        st_path = new char[1024];
        cimg_std::memset(st_path,0,1024);
        bool path_found = false;
        cimg_std::FILE *file = 0;
#if cimg_OS==2
        if (!path_found) {
          cimg_std::sprintf(st_path,".\\gzip.exe");
          if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
        }
        if (!path_found) cimg_std::strcpy(st_path,"gzip.exe");
#else
        if (!path_found) {
          cimg_std::sprintf(st_path,"./gzip");
          if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
        }
        if (!path_found) cimg_std::strcpy(st_path,"gzip");
#endif
        winformat_string(st_path);
      }
      return st_path;
    }

    //! Return or set path to the 'gunzip' command.
    inline const char *gunzip_path(const char *const user_path=0, const bool reinit_path=false) {
      static char *st_path = 0;
      if (reinit_path && st_path) { delete[] st_path; st_path = 0; }
      if (user_path) {
        if (!st_path) st_path = new char[1024];
        cimg_std::memset(st_path,0,1024);
        cimg_std::strncpy(st_path,user_path,1023);
      } else if (!st_path) {
        st_path = new char[1024];
        cimg_std::memset(st_path,0,1024);
        bool path_found = false;
        cimg_std::FILE *file = 0;
#if cimg_OS==2
        if (!path_found) {
          cimg_std::sprintf(st_path,".\\gunzip.exe");
          if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
        }
        if (!path_found) cimg_std::strcpy(st_path,"gunzip.exe");
#else
        if (!path_found) {
          cimg_std::sprintf(st_path,"./gunzip");
          if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
        }
        if (!path_found) cimg_std::strcpy(st_path,"gunzip");
#endif
        winformat_string(st_path);
      }
      return st_path;
    }

    //! Return or set path to the 'dcraw' command.
    inline const char *dcraw_path(const char *const user_path=0, const bool reinit_path=false) {
      static char *st_path = 0;
      if (reinit_path && st_path) { delete[] st_path; st_path = 0; }
      if (user_path) {
        if (!st_path) st_path = new char[1024];
        cimg_std::memset(st_path,0,1024);
        cimg_std::strncpy(st_path,user_path,1023);
      } else if (!st_path) {
        st_path = new char[1024];
        cimg_std::memset(st_path,0,1024);
        bool path_found = false;
        cimg_std::FILE *file = 0;
#if cimg_OS==2
        if (!path_found) {
          cimg_std::sprintf(st_path,".\\dcraw.exe");
          if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
        }
        if (!path_found) cimg_std::strcpy(st_path,"dcraw.exe");
#else
        if (!path_found) {
          cimg_std::sprintf(st_path,"./dcraw");
          if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
        }
        if (!path_found) cimg_std::strcpy(st_path,"dcraw");
#endif
        winformat_string(st_path);
      }
      return st_path;
    }

    //! Split a filename into two strings 'body' and 'extension'.
    inline const char *split_filename(const char *const filename, char *const body=0) {
      if (!filename) { if (body) body[0]='\0'; return 0; }
      int l = cimg::strfind(filename,'.');
      if (l>=0) { if (body) { cimg_std::strncpy(body,filename,l); body[l]='\0'; }}
      else { if (body) cimg_std::strcpy(body,filename); l = (int)cimg::strlen(filename)-1; }
      return filename+l+1;
    }

    //! Create a numbered version of a filename.
    inline char* number_filename(const char *const filename, const int number, const unsigned int n, char *const string) {
      if (!filename) { if (string) string[0]='\0'; return 0; }
      char format[1024],body[1024];
      const char *ext = cimg::split_filename(filename,body);
      if (n>0) cimg_std::sprintf(format,"%s_%%.%ud.%s",body,n,ext);
      else cimg_std::sprintf(format,"%s_%%d.%s",body,ext);
      cimg_std::sprintf(string,format,number);
      return string;
    }

    //! Open a file, and check for possible errors.
    inline cimg_std::FILE *fopen(const char *const path, const char *const mode) {
      if(!path || !mode)
        throw CImgArgumentException("cimg::fopen() : File '%s', cannot open with mode '%s'.",
                                    path?path:"(null)",mode?mode:"(null)");
      if (path[0]=='-') return (mode[0]=='r')?stdin:stdout;
      cimg_std::FILE *dest = cimg_std::fopen(path,mode);
      if (!dest)
        throw CImgIOException("cimg::fopen() : File '%s', cannot open file %s",
                              path,mode[0]=='r'?"for reading.":(mode[0]=='w'?"for writing.":"."),path);
      return dest;
    }

    //! Close a file, and check for possible errors.
    inline int fclose(cimg_std::FILE *file) {
      if (!file) warn("cimg::fclose() : Can't close (null) file");
      if (!file || file==stdin || file==stdout) return 0;
      const int errn = cimg_std::fclose(file);
      if (errn!=0) warn("cimg::fclose() : Error %d during file closing",errn);
      return errn;
    }

    //! Try to guess the image format of a filename, using its magick numbers.
    inline const char *file_type(cimg_std::FILE *const file, const char *const filename) {
      static const char
        *const _pnm = "pnm",
        *const _bmp = "bmp",
        *const _gif = "gif",
        *const _jpeg = "jpeg",
        *const _off = "off",
        *const _pan = "pan",
        *const _png = "png",
        *const _tiff = "tiff";
      if (!filename && !file) throw CImgArgumentException("cimg::file_type() : Cannot load (null) filename.");
      cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"rb");
      const char *ftype = 0, *head;
      char header[2048], item[1024];
      const unsigned char *const uheader = (unsigned char*)header;
      int err;
      const unsigned int siz = (unsigned int)cimg_std::fread(header,2048,1,nfile);   // Read first 2048 bytes.
      if (!file) cimg::fclose(nfile);
      if (!ftype) { // Check for BMP format.
        if (header[0]=='B' && header[1]=='M') ftype = _bmp;
      }
      if (!ftype) { // Check for GIF format.
        if (header[0]=='G' && header[1]=='I' && header[2]=='F' && header[3]=='8' && header[5]=='a' &&
            (header[4]=='7' || header[4]=='9')) ftype = _gif;
      }
      if (!ftype) { // Check for JPEG format.
        if (uheader[0]==0xFF && uheader[1]==0xD8 && uheader[2]==0xFF) ftype = _jpeg;
      }
      if (!ftype) { // Check for OFF format.
        if (header[0]=='O' && header[1]=='F' && header[2]=='F' && header[3]=='\n') ftype = _off;
      }
      if (!ftype) { // Check for PAN format.
        if (header[0]=='P' && header[1]=='A' && header[2]=='N' && header[3]=='D' && header[4]=='O' &&
            header[5]=='R' && header[6]=='E') ftype = _pan;
      }
      if (!ftype) { // Check for PNG format.
        if (uheader[0]==0x89 && uheader[1]==0x50 && uheader[2]==0x4E && uheader[3]==0x47 &&
            uheader[4]==0x0D && uheader[5]==0x0A && uheader[6]==0x1A && uheader[7]==0x0A) ftype = _png;
      }
      if (!ftype) { // Check for PNM format.
        head = header;
        while (head<header+siz && (err=cimg_std::sscanf(head,"%1023[^\n]",item))!=EOF && (item[0]=='#' || !err))
          head+=1+(err?cimg::strlen(item):0);
        if (cimg_std::sscanf(item," P%d",&err)==1) ftype = _pnm;
      }
      if (!ftype) { // Check for TIFF format.
        if ((uheader[0]==0x49 && uheader[1]==0x49) || (uheader[0]==0x4D && uheader[1]==0x4D)) ftype = _tiff;
      }
      return ftype;
    }

    //! Read file data, and check for possible errors.
    template<typename T>
    inline int fread(T *const ptr, const unsigned int nmemb, cimg_std::FILE *stream) {
      if (!ptr || nmemb<=0 || !stream)
        throw CImgArgumentException("cimg::fread() : Can't read %u x %u bytes of file pointer '%p' in buffer '%p'",
                                    nmemb,sizeof(T),stream,ptr);
      const unsigned long wlimitT = 63*1024*1024, wlimit = wlimitT/sizeof(T);
      unsigned int toread = nmemb, alread = 0, ltoread = 0, lalread = 0;
      do {
        ltoread = (toread*sizeof(T))<wlimitT?toread:wlimit;
        lalread = (unsigned int)cimg_std::fread((void*)(ptr+alread),sizeof(T),ltoread,stream);
        alread+=lalread;
        toread-=lalread;
      } while (ltoread==lalread && toread>0);
      if (toread>0) warn("cimg::fread() : File reading problems, only %u/%u elements read",alread,nmemb);
      return alread;
    }

    //! Write data to a file, and check for possible errors.
    template<typename T>
    inline int fwrite(const T *ptr, const unsigned int nmemb, cimg_std::FILE *stream) {
      if (!ptr || !stream)
        throw CImgArgumentException("cimg::fwrite() : Can't write %u x %u bytes of file pointer '%p' from buffer '%p'",
                                    nmemb,sizeof(T),stream,ptr);
      if (nmemb<=0) return 0;
      const unsigned long wlimitT = 63*1024*1024, wlimit = wlimitT/sizeof(T);
      unsigned int towrite = nmemb, alwrite = 0, ltowrite = 0, lalwrite = 0;
      do {
        ltowrite = (towrite*sizeof(T))<wlimitT?towrite:wlimit;
        lalwrite = (unsigned int)cimg_std::fwrite((void*)(ptr+alwrite),sizeof(T),ltowrite,stream);
        alwrite+=lalwrite;
        towrite-=lalwrite;
      } while (ltowrite==lalwrite && towrite>0);
      if (towrite>0) warn("cimg::fwrite() : File writing problems, only %u/%u elements written",alwrite,nmemb);
      return alwrite;
    }

    inline const char* option(const char *const name, const int argc, const char *const *const argv,
                              const char *defaut, const char *const usage=0) {
      static bool first = true, visu = false;
      const char *res = 0;
      if (first) {
        first=false;
        visu = (cimg::option("-h",argc,argv,(char*)0)!=0);
        visu |= (cimg::option("-help",argc,argv,(char*)0)!=0);
        visu |= (cimg::option("--help",argc,argv,(char*)0)!=0);
      }
      if (!name && visu) {
        if (usage) {
          cimg_std::fprintf(cimg_stdout,"\n %s%s%s",cimg::t_red,cimg::basename(argv[0]),cimg::t_normal);
          cimg_std::fprintf(cimg_stdout," : %s",usage);
          cimg_std::fprintf(cimg_stdout," (%s, %s)\n\n",__DATE__,__TIME__);
        }
        if (defaut) cimg_std::fprintf(cimg_stdout,"%s\n",defaut);
      }
      if (name) {
        if (argc>0) {
          int k = 0;
          while (k<argc && cimg::strcmp(argv[k],name)) ++k;
          res = (k++==argc?defaut:(k==argc?argv[--k]:argv[k]));
        } else res = defaut;
        if (visu && usage) cimg_std::fprintf(cimg_stdout,"    %s%-16s%s %-24s %s%s%s\n",
                                        cimg::t_bold,name,cimg::t_normal,res?res:"0",cimg::t_green,usage,cimg::t_normal);
      }
      return res;
    }

    inline bool option(const char *const name, const int argc, const char *const *const argv,
                       const bool defaut, const char *const usage=0) {
      const char *s = cimg::option(name,argc,argv,(char*)0);
      const bool res = s?(cimg::strcasecmp(s,"false") && cimg::strcasecmp(s,"off") && cimg::strcasecmp(s,"0")):defaut;
      cimg::option(name,0,0,res?"true":"false",usage);
      return res;
    }

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

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

    inline float option(const char *const name, const int argc, const char *const *const argv,
                        const float defaut, const char *const usage=0) {
      const char *s = cimg::option(name,argc,argv,(char*)0);
      const float res = s?cimg::atof(s):defaut;
      char tmp[256];
      cimg_std::sprintf(tmp,"%g",res);
      cimg::option(name,0,0,tmp,usage);
      return res;
    }

    inline double option(const char *const name, const int argc, const char *const *const argv,
                         const double defaut, const char *const usage=0) {
      const char *s = cimg::option(name,argc,argv,(char*)0);
      const double res = s?cimg::atof(s):defaut;
      char tmp[256];
      cimg_std::sprintf(tmp,"%g",res);
      cimg::option(name,0,0,tmp,usage);
      return res;
    }

    inline const char* argument(const unsigned int nb, const int argc, const char *const *const argv, const unsigned int nb_singles=0, ...) {
      for (int k = 1, pos = 0; k<argc;) {
        const char *const item = argv[k];
        bool option = (*item=='-'), single_option = false;
        if (option) {
          va_list ap;
          va_start(ap,nb_singles);
          for (unsigned int i=0; i<nb_singles; ++i) if (!cimg::strcasecmp(item,va_arg(ap,char*))) { single_option = true; break; }
          va_end(ap);
        }
        if (option) { ++k; if (!single_option) ++k; }
        else { if (pos++==(int)nb) return item; else ++k; }
      }
      return 0;
    }

    //! Print informations about %CImg environement variables.
    /**
       Printing is done on the standard error output.
    **/
    inline void info() {
      char tmp[1024] = { 0 };
      cimg_std::fprintf(cimg_stdout,"\n %sCImg Library %u.%u.%u%s, compiled %s ( %s ) with the following flags :\n\n",
                   cimg::t_red,cimg_version/100,(cimg_version/10)%10,cimg_version%10,
                   cimg::t_normal,__DATE__,__TIME__);

      cimg_std::fprintf(cimg_stdout,"  > Operating System :       %s%-13s%s %s('cimg_OS'=%d)%s\n",
                   cimg::t_bold,
                   cimg_OS==1?"Unix":(cimg_OS==2?"Windows":"Unknow"),
                   cimg::t_normal,cimg::t_green,
                   cimg_OS,
                   cimg::t_normal);

      cimg_std::fprintf(cimg_stdout,"  > CPU endianness :         %s%s Endian%s\n",
                   cimg::t_bold,
                   cimg::endianness()?"Big":"Little",
                   cimg::t_normal);

#ifdef cimg_use_visualcpp6
      cimg_std::fprintf(cimg_stdout,"  > Using Visual C++ 6.0 :       %s%-13s%s %s('cimg_use_visualcpp6' defined)%s\n",
                   cimg::t_bold,"Yes",cimg::t_normal,cimg::t_green,cimg::t_normal);
#endif

      cimg_std::fprintf(cimg_stdout,"  > Debug messages :         %s%-13s%s %s('cimg_debug'=%d)%s\n",
                   cimg::t_bold,
                   cimg_debug==0?"Quiet":(cimg_debug==1?"Console":(cimg_debug==2?"Dialog":(cimg_debug==3?"Console+Warnings":"Dialog+Warnings"))),
                   cimg::t_normal,cimg::t_green,
                   cimg_debug,
                   cimg::t_normal);

      cimg_std::fprintf(cimg_stdout,"  > Stricts warnings :       %s%-13s%s %s('cimg_strict_warnings' %s)%s\n",
                   cimg::t_bold,
#ifdef cimg_strict_warnings
                   "Yes",cimg::t_normal,cimg::t_green,"defined",
#else
                   "No",cimg::t_normal,cimg::t_green,"undefined",
#endif
                   cimg::t_normal);

      cimg_std::fprintf(cimg_stdout,"  > Using VT100 messages :   %s%-13s%s %s('cimg_use_vt100' %s)%s\n",
                   cimg::t_bold,
#ifdef cimg_use_vt100
                   "Yes",cimg::t_normal,cimg::t_green,"defined",
#else
                   "No",cimg::t_normal,cimg::t_green,"undefined",
#endif
                   cimg::t_normal);

      cimg_std::fprintf(cimg_stdout,"  > Display type :           %s%-13s%s %s('cimg_display'=%d)%s\n",
                   cimg::t_bold,
                   cimg_display==0?"No display":
                   (cimg_display==1?"X11":
                    (cimg_display==2?"Windows GDI":
                     (cimg_display==3?"Carbon":"Unknow"))),
                   cimg::t_normal,cimg::t_green,
                   cimg_display,
                   cimg::t_normal);

#if cimg_display==1
      cimg_std::fprintf(cimg_stdout,"  > Using XShm for X11 :     %s%-13s%s %s('cimg_use_xshm' %s)%s\n",
                   cimg::t_bold,
#ifdef cimg_use_xshm
                   "Yes",cimg::t_normal,cimg::t_green,"defined",
#else
                   "No",cimg::t_normal,cimg::t_green,"undefined",
#endif
                   cimg::t_normal);

      cimg_std::fprintf(cimg_stdout,"  > Using XRand for X11 :    %s%-13s%s %s('cimg_use_xrandr' %s)%s\n",
                   cimg::t_bold,
#ifdef cimg_use_xrandr
                   "Yes",cimg::t_normal,cimg::t_green,"defined",
#else
                   "No",cimg::t_normal,cimg::t_green,"undefined",
#endif
                   cimg::t_normal);
#endif
      cimg_std::fprintf(cimg_stdout,"  > Using OpenMP :           %s%-13s%s %s('cimg_use_openmp' %s)%s\n",
                   cimg::t_bold,
#ifdef cimg_use_openmp
                   "Yes",cimg::t_normal,cimg::t_green,"defined",
#else
                   "No",cimg::t_normal,cimg::t_green,"undefined",
#endif
                   cimg::t_normal);
      cimg_std::fprintf(cimg_stdout,"  > Using PNG library :      %s%-13s%s %s('cimg_use_png' %s)%s\n",
                   cimg::t_bold,
#ifdef cimg_use_png
                   "Yes",cimg::t_normal,cimg::t_green,"defined",
#else
                   "No",cimg::t_normal,cimg::t_green,"undefined",
#endif
                   cimg::t_normal);
      cimg_std::fprintf(cimg_stdout,"  > Using JPEG library :     %s%-13s%s %s('cimg_use_jpeg' %s)%s\n",
                   cimg::t_bold,
#ifdef cimg_use_jpeg
                   "Yes",cimg::t_normal,cimg::t_green,"defined",
#else
                   "No",cimg::t_normal,cimg::t_green,"undefined",
#endif
                   cimg::t_normal);

      cimg_std::fprintf(cimg_stdout,"  > Using TIFF library :     %s%-13s%s %s('cimg_use_tiff' %s)%s\n",
                   cimg::t_bold,
#ifdef cimg_use_tiff
                   "Yes",cimg::t_normal,cimg::t_green,"defined",
#else
                   "No",cimg::t_normal,cimg::t_green,"undefined",
#endif
                   cimg::t_normal);

      cimg_std::fprintf(cimg_stdout,"  > Using Magick++ library : %s%-13s%s %s('cimg_use_magick' %s)%s\n",
                   cimg::t_bold,
#ifdef cimg_use_magick
                   "Yes",cimg::t_normal,cimg::t_green,"defined",
#else
                   "No",cimg::t_normal,cimg::t_green,"undefined",
#endif
                   cimg::t_normal);

      cimg_std::fprintf(cimg_stdout,"  > Using FFTW3 library :    %s%-13s%s %s('cimg_use_fftw3' %s)%s\n",
                   cimg::t_bold,
#ifdef cimg_use_fftw3
                   "Yes",cimg::t_normal,cimg::t_green,"defined",
#else
                   "No",cimg::t_normal,cimg::t_green,"undefined",
#endif
                   cimg::t_normal);

      cimg_std::fprintf(cimg_stdout,"  > Using LAPACK library :   %s%-13s%s %s('cimg_use_lapack' %s)%s\n",
                   cimg::t_bold,
#ifdef cimg_use_lapack
                   "Yes",cimg::t_normal,cimg::t_green,"defined",
#else
                   "No",cimg::t_normal,cimg::t_green,"undefined",
#endif
                   cimg::t_normal);

      cimg_std::sprintf(tmp,"\"%.1020s\"",cimg::imagemagick_path());
      cimg_std::fprintf(cimg_stdout,"  > Path of ImageMagick :    %s%-13s%s\n",
                   cimg::t_bold,
                   tmp,
                   cimg::t_normal);

      cimg_std::sprintf(tmp,"\"%.1020s\"",cimg::graphicsmagick_path());
      cimg_std::fprintf(cimg_stdout,"  > Path of GraphicsMagick : %s%-13s%s\n",
                   cimg::t_bold,
                   tmp,
                   cimg::t_normal);

      cimg_std::sprintf(tmp,"\"%.1020s\"",cimg::medcon_path());
      cimg_std::fprintf(cimg_stdout,"  > Path of 'medcon' :       %s%-13s%s\n",
                   cimg::t_bold,
                   tmp,
                   cimg::t_normal);

      cimg_std::sprintf(tmp,"\"%.1020s\"",cimg::temporary_path());
      cimg_std::fprintf(cimg_stdout,"  > Temporary path :         %s%-13s%s\n",
                   cimg::t_bold,
                   tmp,
                   cimg::t_normal);

      cimg_std::fprintf(cimg_stdout,"\n");
    }

    // Declare LAPACK function signatures if necessary.
    //
#ifdef cimg_use_lapack
    template<typename T>
    inline void getrf(int &N, T *lapA, int *IPIV, int &INFO) {
      dgetrf_(&N,&N,lapA,&N,IPIV,&INFO);
    }

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

    template<typename T>
    inline void getri(int &N, T *lapA, int *IPIV, T* WORK, int &LWORK, int &INFO) {
      dgetri_(&N,lapA,&N,IPIV,WORK,&LWORK,&INFO);
    }

    inline void getri(int &N, float *lapA, int *IPIV, float* WORK, int &LWORK, int &INFO) {
      sgetri_(&N,lapA,&N,IPIV,WORK,&LWORK,&INFO);
    }

    template<typename T>
    inline void gesvd(char &JOB, int &M, int &N, T *lapA, int &MN,
                      T *lapS, T *lapU, T *lapV, T *WORK, int &LWORK, int &INFO) {
      dgesvd_(&JOB,&JOB,&M,&N,lapA,&MN,lapS,lapU,&M,lapV,&N,WORK,&LWORK,&INFO);
    }

    inline void gesvd(char &JOB, int &M, int &N, float *lapA, int &MN,
                      float *lapS, float *lapU, float *lapV, float *WORK, int &LWORK, int &INFO) {
      sgesvd_(&JOB,&JOB,&M,&N,lapA,&MN,lapS,lapU,&M,lapV,&N,WORK,&LWORK,&INFO);
    }

    template<typename T>
    inline void getrs(char &TRANS, int &N, T *lapA, int *IPIV, T *lapB, int &INFO) {
      int one = 1;
      dgetrs_(&TRANS,&N,&one,lapA,&N,IPIV,lapB,&N,&INFO);
    }

    inline void getrs(char &TRANS, int &N, float *lapA, int *IPIV, float *lapB, int &INFO) {
      int one = 1;
      sgetrs_(&TRANS,&N,&one,lapA,&N,IPIV,lapB,&N,&INFO);
    }

    template<typename T>
    inline void syev(char &JOB, char &UPLO, int &N, T *lapA, T *lapW, T *WORK, int &LWORK, int &INFO) {
      dsyev_(&JOB,&UPLO,&N,lapA,&N,lapW,WORK,&LWORK,&INFO);
    }

    inline void syev(char &JOB, char &UPLO, int &N, float *lapA, float *lapW, float *WORK, int &LWORK, int &INFO) {
      ssyev_(&JOB,&UPLO,&N,lapA,&N,lapW,WORK,&LWORK,&INFO);
    }
#endif

    // End of the 'cimg' namespace
  }

  /*------------------------------------------------
   #
   #
   #   Definition of mathematical operators and
   #   external functions.
   #
   #
   -------------------------------------------------*/
  //
  // These functions are extern to any classes and can be used for a "functional-style" programming,
  // such as writing :
  //                     cos(img);
  // instead of          img.get_cos();
  //
  // Note that only the arithmetic operators and functions are implemented here.
  //

#ifdef cimg_use_visualcpp6
  template<typename t>
  inline CImg<t> operator+(const CImg<t>& img, const t val) {
    return CImg<t>(img,false)+=val;
  }
#else
  template<typename t1, typename t2>
  inline CImg<typename cimg::superset<t1,t2>::type> operator+(const CImg<t1>& img, const t2 val) {
    typedef typename cimg::superset<t1,t2>::type t1t2;
    return CImg<t1t2>(img,false)+=val;
 }
#endif

#ifdef cimg_use_visualcpp6
  template<typename t>
  inline CImg<t> operator+(const t val, const CImg<t>& img) {
    return img + val;
  }
#else
  template<typename t1, typename t2>
  inline CImg<typename cimg::superset<t1,t2>::type> operator+(const t1 val, const CImg<t2>& img) {
    return img + val;
  }
#endif

#ifdef cimg_use_visualcpp6
  template<typename t>
  inline CImgList<t> operator+(const CImgList<t>& list, const t val) {
    return CImgList<t>(list)+=val;
  }
#else
  template<typename t1, typename t2>
  inline CImgList<typename cimg::superset<t1,t2>::type> operator+(const CImgList<t1>& list, const t2 val) {
    typedef typename cimg::superset<t1,t2>::type t1t2;
    return CImgList<t1t2>(list)+=val;
  }
#endif

#ifdef cimg_use_visualcpp6
  template<typename t>
  inline CImgList<t> operator+(const t val, const CImgList<t>& list) {
    return list + val;
  }
#else
  template<typename t1, typename t2>
  inline CImgList<typename cimg::superset<t1,t2>::type> operator+(const t1 val, const CImgList<t2>& list) {
    return list + val;
  }
#endif

  template<typename t1, typename t2>
  inline CImg<typename cimg::superset<t1,t2>::type> operator+(const CImg<t1>& img1, const CImg<t2>& img2) {
    typedef typename cimg::superset<t1,t2>::type t1t2;
    return CImg<t1t2>(img1,false)+=img2;
  }

  template<typename t1, typename t2>
  inline CImgList<typename cimg::superset<t1,t2>::type> operator+(const CImg<t1>& img, const CImgList<t2>& list) {
    typedef typename cimg::superset<t1,t2>::type t1t2;
    return CImgList<t1t2>(list)+=img;
  }

  template<typename t1, typename t2>
  inline CImgList<typename cimg::superset<t1,t2>::type> operator+(const CImgList<t1>& list, const CImg<t2>& img) {
    return img + list;
  }

  template<typename t1, typename t2>
  inline CImgList<typename cimg::superset<t1,t2>::type> operator+(const CImgList<t1>& list1, const CImgList<t2>& list2) {
    typedef typename cimg::superset<t1,t2>::type t1t2;
    return CImgList<t1t2>(list1)+=list2;
  }

#ifdef cimg_use_visualcpp6
  template<typename t>
  inline CImg<t> operator-(const CImg<t>& img, const t val) {
    return CImg<t>(img,false)-=val;
  }
#else
  template<typename t1, typename t2>
  inline CImg<typename cimg::superset<t1,t2>::type> operator-(const CImg<t1>& img, const t2 val) {
    typedef typename cimg::superset<t1,t2>::type t1t2;
    return CImg<t1t2>(img,false)-=val;
  }
#endif

#ifdef cimg_use_visualcpp6
  template<typename t>
  inline CImg<t> operator-(const t val, const CImg<t>& img) {
    return CImg<t>(img.width,img.height,img.depth,img.dim,val)-=img;
  }
#else
  template<typename t1, typename t2>
  inline CImg<typename cimg::superset<t1,t2>::type> operator-(const t1 val, const CImg<t2>& img) {
    typedef typename cimg::superset<t1,t2>::type t1t2;
    return CImg<t1t2>(img.width,img.height,img.depth,img.dim,(t1t2)val)-=img;
  }
#endif

#ifdef cimg_use_visualcpp6
  template<typename t>
  inline CImgList<t> operator-(const CImgList<t>& list, const t val) {
    return CImgList<t>(list)-=val;
  }
#else
  template<typename t1, typename t2>
  inline CImgList<typename cimg::superset<t1,t2>::type> operator-(const CImgList<t1>& list, const t2 val) {
    typedef typename cimg::superset<t1,t2>::type t1t2;
    return CImgList<t1t2>(list)-=val;
  }
#endif

#ifdef cimg_use_visualcpp6
  template<typename t>
  inline CImgList<double> operator-(const t val, const CImgList<t>& list) {
    CImgList<t> res(list.size);
    cimglist_for(res,l) res[l] = val - list[l];
    return res;
  }
#else
  template<typename t1, typename t2>
  inline CImgList<typename cimg::superset<t1,t2>::type> operator-(const t1 val, const CImgList<t2>& list) {
    typedef typename cimg::superset<t1,t2>::type t1t2;
    CImgList<t1t2> res(list.size);
    cimglist_for(res,l) res[l] = val - list[l];
    return res;
  }
#endif

  template<typename t1, typename t2>
  inline CImg<typename cimg::superset<t1,t2>::type> operator-(const CImg<t1>& img1, const CImg<t2>& img2) {
    typedef typename cimg::superset<t1,t2>::type t1t2;
    return CImg<t1t2>(img1,false)-=img2;
  }

  template<typename t1, typename t2>
  inline CImgList<typename cimg::superset<t1,t2>::type> operator-(const CImg<t1>& img, const CImgList<t2>& list) {
    typedef typename cimg::superset<t1,t2>::type t1t2;
    CImgList<t1t2> res(list.size);
    cimglist_for(res,l) res[l] = img - list[l];
    return res;
  }

  template<typename t1, typename t2>
  inline CImgList<typename cimg::superset<t1,t2>::type> operator-(const CImgList<t1>& list, const CImg<t2>& img) {
    typedef typename cimg::superset<t1,t2>::type t1t2;
    return CImgList<t1t2>(list)-=img;
  }

  template<typename t1, typename t2>
  inline CImgList<typename cimg::superset<t1,t2>::type> operator-(const CImgList<t1>& list1, const CImgList<t2>& list2) {
    typedef typename cimg::superset<t1,t2>::type t1t2;
    return CImgList<t1t2>(list1)-=list2;
  }

#ifdef cimg_use_visualcpp6
  template<typename t>
  inline CImg<t> operator*(const CImg<t>& img, const double val) {
    return CImg<t>(img,false)*=val;
  }
#else
  template<typename t1, typename t2>
  inline CImg<typename cimg::superset<t1,t2>::type> operator*(const CImg<t1>& img, const t2 val) {
    typedef typename cimg::superset<t1,t2>::type t1t2;
    return CImg<t1t2>(img,false)*=val;
  }
#endif

#ifdef cimg_use_visualcpp6
  template<typename t>
  inline CImg<t> operator*(const double val, const CImg<t>& img) {
    return img*val;
  }
#else
  template<typename t1, typename t2>
  inline CImg<typename cimg::superset<t1,t2>::type> operator*(const t1 val, const CImg<t2>& img) {
    return img*val;
  }
#endif

#ifdef cimg_use_visualcpp6
  template<typename t>
  inline CImgList<t> operator*(const CImgList<t>& list, const double val) {
    return CImgList<t>(list)*=val;
  }
#else
  template<typename t1, typename t2>
  inline CImgList<typename cimg::superset<t1,t2>::type> operator*(const CImgList<t1>& list, const t2 val) {
    typedef typename cimg::superset<t1,t2>::type t1t2;
    return CImgList<t1t2>(list)*=val;
  }
#endif

#ifdef cimg_use_visualcpp6
  template<typename t>
  inline CImgList<t> operator*(const double val, const CImgList<t>& list) {
    return list*val;
  }
#else
  template<typename t1, typename t2>
  inline CImgList<typename cimg::superset<t1,t2>::type> operator*(const t1 val, const CImgList<t2>& list) {
    return list*val;
  }
#endif

  template<typename t1, typename t2>
  inline CImg<typename cimg::superset<t1,t2>::type> operator*(const CImg<t1>& img1, const CImg<t2>& img2) {
    typedef typename cimg::superset<t1,t2>::type t1t2;
    if (img1.width!=img2.height)
      throw CImgArgumentException("operator*() : can't multiply a matrix (%ux%u) by a matrix (%ux%u)",
                                  img1.width,img1.height,img2.width,img2.height);
    CImg<t1t2> res(img2.width,img1.height);
    t1t2 val;
#ifdef cimg_use_openmp
#pragma omp parallel for if (img1.size()>=1000 && img2.size()>=1000) private(val)
#endif
    cimg_forXY(res,i,j) { val = 0; cimg_forX(img1,k) val+=img1(k,j)*img2(i,k); res(i,j) = val; }
    return res;
  }

  template<typename t1, typename t2>
  inline CImgList<typename cimg::superset<t1,t2>::type> operator*(const CImg<t1>& img, const CImgList<t2>& list) {
    typedef typename cimg::superset<t1,t2>::type t1t2;
    CImgList<t1t2> res(list.size);
    cimglist_for(res,l) res[l] = img*list[l];
    return res;
  }

  template<typename t1, typename t2>
  inline CImgList<typename cimg::superset<t1,t2>::type> operator*(const CImgList<t1>& list, const CImg<t2>& img) {
    typedef typename cimg::superset<t1,t2>::type t1t2;
    CImgList<t1t2> res(list.size);
    cimglist_for(res,l) res[l] = list[l]*img;
    return res;
  }

  template<typename t1, typename t2>
  inline CImgList<typename cimg::superset<t1,t2>::type> operator*(const CImgList<t1>& list1, const CImgList<t2>& list2) {
    typedef typename cimg::superset<t1,t2>::type t1t2;
    CImgList<t1t2> res(cimg::min(list1.size,list2.size));
    cimglist_for(res,l) res[l] = list1[l]*list2[l];
    return res;
  }

#ifdef cimg_use_visualcpp6
  template<typename t>
  inline CImg<t> operator/(const CImg<t>& img, const double val) {
    return CImg<t>(img,false)/=val;
  }
#else
  template<typename t1, typename t2>
  inline CImg<typename cimg::superset<t1,t2>::type> operator/(const CImg<t1>& img, const t2 val) {
    typedef typename cimg::superset<t1,t2>::type t1t2;
    return CImg<t1t2>(img,false)/=val;
  }
#endif

#ifdef cimg_use_visualcpp6
  template<typename t>
  inline CImg<t> operator/(const double val, CImg<t>& img) {
    return val*img.get_invert();
  }
#else
  template<typename t1, typename t2>
  inline CImg<typename cimg::superset<t1,t2>::type> operator/(const t1 val, CImg<t2>& img) {
    return val*img.get_invert();
  }
#endif

#ifdef cimg_use_visualcpp6
  template<typename t>
  inline CImgList<t> operator/(const CImgList<t>& list, const double val) {
    return CImgList<t>(list)/=val;
  }
#else
  template<typename t1, typename t2>
  inline CImgList<typename cimg::superset<t1,t2>::type> operator/(const CImgList<t1>& list, const t2 val) {
    typedef typename cimg::superset<t1,t2>::type t1t2;
    return CImgList<t1t2>(list)/=val;
  }
#endif

#ifdef cimg_use_visualcpp6
  template<typename t>
  inline CImgList<t> operator/(const double val, const CImgList<t>& list) {
    CImgList<t> res(list.size);
    cimglist_for(res,l) res[l] = val/list[l];
    return res;
  }
#else
  template<typename t1, typename t2>
  inline CImgList<typename cimg::superset<t1,t2>::type> operator/(const t1 val, const CImgList<t2>& list) {
    typedef typename cimg::superset<t1,t2>::type t1t2;
    CImgList<t1t2> res(list.size);
    cimglist_for(res,l) res[l] = val/list[l];
    return res;
  }
#endif

  template<typename t1, typename t2>
  inline CImg<typename cimg::superset<t1,t2>::type> operator/(const CImg<t1>& img1, const CImg<t2>& img2) {
    typedef typename cimg::superset<t1,t2>::type t1t2;
    return CImg<t1t2>(img1,false)*=img2.get_invert();
  }

  template<typename t1, typename t2>
  inline CImg<typename cimg::superset<t1,t2>::type> operator/(const CImg<t1>& img, const CImgList<t2>& list) {
    typedef typename cimg::superset<t1,t2>::type t1t2;
    CImgList<t1t2> res(list.size);
    cimglist_for(res,l) res[l] = img/list[l];
    return res;
  }

  template<typename t1, typename t2>
  inline CImgList<typename cimg::superset<t1,t2>::type> operator/(const CImgList<t1>& list, const CImg<t2>& img) {
    typedef typename cimg::superset<t1,t2>::type t1t2;
    return CImgList<t1t2>(list)/=img;
  }

  template<typename t1, typename t2>
  inline CImgList<typename cimg::superset<t1,t2>::type> operator/(const CImgList<t1>& list1, const CImgList<t2>& list2) {
    typedef typename cimg::superset<t1,t2>::type t1t2;
    return CImgList<t1t2>(list1)/=list2;
  }

  template<typename T>
  inline CImg<_cimg_Tfloat> sqr(const CImg<T>& instance) {
    return instance.get_sqr();
  }

  template<typename T>
  inline CImg<_cimg_Tfloat> sqrt(const CImg<T>& instance) {
    return instance.get_sqrt();
  }

  template<typename T>
  inline CImg<_cimg_Tfloat> exp(const CImg<T>& instance) {
    return instance.get_exp();
  }

  template<typename T>
  inline CImg<_cimg_Tfloat> log(const CImg<T>& instance) {
    return instance.get_log();
  }

  template<typename T>
  inline CImg<_cimg_Tfloat> log10(const CImg<T>& instance) {
    return instance.get_log10();
  }

  template<typename T>
  inline CImg<_cimg_Tfloat> abs(const CImg<T>& instance) {
    return instance.get_abs();
  }

  template<typename T>
  inline CImg<_cimg_Tfloat> cos(const CImg<T>& instance) {
    return instance.get_cos();
  }

  template<typename T>
  inline CImg<_cimg_Tfloat> sin(const CImg<T>& instance) {
    return instance.get_sin();
  }

  template<typename T>
  inline CImg<_cimg_Tfloat> tan(const CImg<T>& instance) {
    return instance.get_tan();
  }

  template<typename T>
  inline CImg<_cimg_Tfloat> acos(const CImg<T>& instance) {
    return instance.get_acos();
  }

  template<typename T>
  inline CImg<_cimg_Tfloat> asin(const CImg<T>& instance) {
    return instance.get_asin();
  }

  template<typename T>
  inline CImg<_cimg_Tfloat> atan(const CImg<T>& instance) {
    return instance.get_atan();
  }

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

  template<typename T>
  inline CImg<_cimg_Tfloat> invert(const CImg<T>& instance) {
    return instance.get_invert();
  }

  template<typename T>
  inline CImg<_cimg_Tfloat> pseudoinvert(const CImg<T>& instance) {
    return instance.get_pseudoinvert();
  }

  /*-------------------------------------------
   #
   #
   #
   # Definition of the CImgDisplay structure
   #
   #
   #
   --------------------------------------------*/

  //! This class represents a window which can display \ref CImg images and handles mouse and keyboard events.
  /**
     Creating a \c CImgDisplay instance opens a window that can be used to display a \c CImg<T> image
     of a \c CImgList<T> image list inside. When a display is created, associated window events
     (such as mouse motion, keyboard and window size changes) are handled and can be easily
     detected by testing specific \c CImgDisplay data fields.
     See \ref cimg_displays for a complete tutorial on using the \c CImgDisplay class.
  **/

  struct CImgDisplay {

    //! Width of the display
    unsigned int width;

    //! Height of the display
    unsigned int height;

    //! Normalization type used for the display
    unsigned int normalization;

    //! Display title
    char* title;

    //! X-pos of the display on the screen
    volatile int window_x;

    //! Y-pos of the display on the screen
    volatile int window_y;

    //! Width of the underlying window
    volatile unsigned int window_width;

    //! Height of the underlying window
    volatile unsigned int window_height;

    //! X-coordinate of the mouse pointer on the display
    volatile int mouse_x;

    //! Y-coordinate of the mouse pointer on the display
    volatile int mouse_y;

    //! Button state of the mouse
    volatile unsigned int buttons[512];
    volatile unsigned int& button;

    //! Wheel state of the mouse
    volatile int wheel;

    //! Key value if pressed
    volatile unsigned int& key;
    volatile unsigned int keys[512];

    //! Key value if released
    volatile unsigned int& released_key;
    volatile unsigned int released_keys[512];

    //! Closed state of the window
    volatile bool is_closed;

    //! Resized state of the window
    volatile bool is_resized;

    //! Moved state of the window
    volatile bool is_moved;

    //! Event state of the window
    volatile bool is_event;

    //! Current state of the corresponding key (exists for all referenced keys).
    volatile bool is_keyESC;
    volatile bool is_keyF1;
    volatile bool is_keyF2;
    volatile bool is_keyF3;
    volatile bool is_keyF4;
    volatile bool is_keyF5;
    volatile bool is_keyF6;
    volatile bool is_keyF7;
    volatile bool is_keyF8;
    volatile bool is_keyF9;
    volatile bool is_keyF10;
    volatile bool is_keyF11;
    volatile bool is_keyF12;
    volatile bool is_keyPAUSE;
    volatile bool is_key1;
    volatile bool is_key2;
    volatile bool is_key3;
    volatile bool is_key4;
    volatile bool is_key5;
    volatile bool is_key6;
    volatile bool is_key7;
    volatile bool is_key8;
    volatile bool is_key9;
    volatile bool is_key0;
    volatile bool is_keyBACKSPACE;
    volatile bool is_keyINSERT;
    volatile bool is_keyHOME;
    volatile bool is_keyPAGEUP;
    volatile bool is_keyTAB;
    volatile bool is_keyQ;
    volatile bool is_keyW;
    volatile bool is_keyE;
    volatile bool is_keyR;
    volatile bool is_keyT;
    volatile bool is_keyY;
    volatile bool is_keyU;
    volatile bool is_keyI;
    volatile bool is_keyO;
    volatile bool is_keyP;
    volatile bool is_keyDELETE;
    volatile bool is_keyEND;
    volatile bool is_keyPAGEDOWN;
    volatile bool is_keyCAPSLOCK;
    volatile bool is_keyA;
    volatile bool is_keyS;
    volatile bool is_keyD;
    volatile bool is_keyF;
    volatile bool is_keyG;
    volatile bool is_keyH;
    volatile bool is_keyJ;
    volatile bool is_keyK;
    volatile bool is_keyL;
    volatile bool is_keyENTER;
    volatile bool is_keySHIFTLEFT;
    volatile bool is_keyZ;
    volatile bool is_keyX;
    volatile bool is_keyC;
    volatile bool is_keyV;
    volatile bool is_keyB;
    volatile bool is_keyN;
    volatile bool is_keyM;
    volatile bool is_keySHIFTRIGHT;
    volatile bool is_keyARROWUP;
    volatile bool is_keyCTRLLEFT;
    volatile bool is_keyAPPLEFT;
    volatile bool is_keyALT;
    volatile bool is_keySPACE;
    volatile bool is_keyALTGR;
    volatile bool is_keyAPPRIGHT;
    volatile bool is_keyMENU;
    volatile bool is_keyCTRLRIGHT;
    volatile bool is_keyARROWLEFT;
    volatile bool is_keyARROWDOWN;
    volatile bool is_keyARROWRIGHT;
    volatile bool is_keyPAD0;
    volatile bool is_keyPAD1;
    volatile bool is_keyPAD2;
    volatile bool is_keyPAD3;
    volatile bool is_keyPAD4;
    volatile bool is_keyPAD5;
    volatile bool is_keyPAD6;
    volatile bool is_keyPAD7;
    volatile bool is_keyPAD8;
    volatile bool is_keyPAD9;
    volatile bool is_keyPADADD;
    volatile bool is_keyPADSUB;
    volatile bool is_keyPADMUL;
    volatile bool is_keyPADDIV;

    //! Fullscreen state of the display
    bool is_fullscreen;

    float fps_fps, min, max;
    unsigned long timer, fps_frames, fps_timer;

#ifdef cimgdisplay_plugin
#include cimgdisplay_plugin
#endif
#ifdef cimgdisplay_plugin1
#include cimgdisplay_plugin1
#endif
#ifdef cimgdisplay_plugin2
#include cimgdisplay_plugin2
#endif
#ifdef cimgdisplay_plugin3
#include cimgdisplay_plugin3
#endif
#ifdef cimgdisplay_plugin4
#include cimgdisplay_plugin4
#endif
#ifdef cimgdisplay_plugin5
#include cimgdisplay_plugin5
#endif
#ifdef cimgdisplay_plugin6
#include cimgdisplay_plugin6
#endif
#ifdef cimgdisplay_plugin7
#include cimgdisplay_plugin7
#endif
#ifdef cimgdisplay_plugin8
#include cimgdisplay_plugin8
#endif

    //! Create an empty display window.
    CImgDisplay():
      width(0),height(0),normalization(0),title(0),
      window_x(0),window_y(0),window_width(0),window_height(0),
      mouse_x(0),mouse_y(0),button(*buttons),wheel(0),key(*keys),released_key(*released_keys),
      is_closed(true),is_resized(false),is_moved(false),is_event(false),is_fullscreen(false),
      min(0),max(0) {}

    //! Create a display window with a specified size \p pwidth x \p height.
    /** \param dimw Width of the display window.
        \param dimh Height of the display window.
        \param title Title of the display window.
        \param normalization_type Normalization type of the display window (0=none, 1=always, 2=once).
        \param fullscreen_flag : Fullscreen mode.
        \param closed_flag : Initially visible mode.
        A black image will be initially displayed in the display window.
    **/
    CImgDisplay(const unsigned int dimw, const unsigned int dimh, const char *title=0,
                const unsigned int normalization_type=3,
                const bool fullscreen_flag=false, const bool closed_flag=false):
      width(0),height(0),normalization(0),title(0),
      window_x(0),window_y(0),window_width(0),window_height(0),
      mouse_x(0),mouse_y(0),button(*buttons),wheel(0),key(*keys),released_key(*released_keys),
      is_closed(true),is_resized(false),is_moved(false),is_event(false),is_fullscreen(false),
      min(0),max(0) {
      assign(dimw,dimh,title,normalization_type,fullscreen_flag,closed_flag);
    }

    //! Create a display window from an image.
    /** \param img : Image that will be used to create the display window.
        \param title : Title of the display window
        \param normalization_type : Normalization type of the display window.
        \param fullscreen_flag : Fullscreen mode.
        \param closed_flag : Initially visible mode.
    **/
    template<typename T>
    CImgDisplay(const CImg<T>& img, const char *title=0,
                const unsigned int normalization_type=3,
                const bool fullscreen_flag=false, const bool closed_flag=false):
      width(0),height(0),normalization(0),title(0),
      window_x(0),window_y(0),window_width(0),window_height(0),
      mouse_x(0),mouse_y(0),button(*buttons),wheel(0),key(*keys),released_key(*released_keys),
      is_closed(true),is_resized(false),is_moved(false),is_event(false),is_fullscreen(false),min(0),max(0) {
      assign(img,title,normalization_type,fullscreen_flag,closed_flag);
    }

    //! Create a display window from an image list.
    /** \param list : The list of images to display.
        \param title : Title of the display window
        \param normalization_type : Normalization type of the display window.
        \param fullscreen_flag : Fullscreen mode.
        \param closed_flag : Initially visible mode.
    **/
    template<typename T>
    CImgDisplay(const CImgList<T>& list, const char *title=0,
                const unsigned int normalization_type=3,
                const bool fullscreen_flag=false, const bool closed_flag=false):
      width(0),height(0),normalization(0),title(0),
      window_x(0),window_y(0),window_width(0),window_height(0),
      mouse_x(0),mouse_y(0),button(*buttons),wheel(0),key(*keys),released_key(*released_keys),
      is_closed(true),is_resized(false),is_moved(false),is_event(false),is_fullscreen(false),min(0),max(0) {
      assign(list,title,normalization_type,fullscreen_flag,closed_flag);
    }

    //! Create a display window by copying another one.
    /**
        \param disp  : Display window to copy.
    **/
    CImgDisplay(const CImgDisplay& disp):
      width(0),height(0),normalization(0),title(0),
      window_x(0),window_y(0),window_width(0),window_height(0),
      mouse_x(0),mouse_y(0),button(*buttons),wheel(0),key(*keys),released_key(*released_keys),
      is_closed(true),is_resized(false),is_moved(false),is_event(false),is_fullscreen(false),min(0),max(0) {
      assign(disp);
    }

    //! Destructor.
    ~CImgDisplay() {
      assign();
    }

    //! Assignment operator.
    CImgDisplay& operator=(const CImgDisplay& disp) {
      return assign(disp);
    }

    //! Return true is display is empty.
    bool is_empty() const {
      return (!width || !height);
    }

    //! Return true if display is not empty.
    operator bool() const {
      return !is_empty();
    }

    //! Return display width.
    int dimx() const {
      return (int)width;
    }

    //! Return display height.
    int dimy() const {
      return (int)height;
    }

    //! Return display window width.
    int window_dimx() const {
      return (int)window_width;
    }

    //! Return display window height.
    int window_dimy() const {
      return (int)window_height;
    }

    //! Return X-coordinate of the window.
    int window_posx() const {
      return window_x;
    }

    //! Return Y-coordinate of the window.
    int window_posy() const {
      return window_y;
    }

    //! Synchronized waiting function. Same as cimg::wait().
    CImgDisplay& wait(const unsigned int milliseconds) {
      cimg::_sleep(milliseconds,timer);
      return *this;
    }

    //! Wait for an event occuring on the current display.
    CImgDisplay& wait() {
      if (!is_empty()) wait(*this);
      return *this;
    }

    //! Wait for any event occuring on the display \c disp1.
    static void wait(CImgDisplay& disp1) {
      disp1.is_event = 0;
      while (!disp1.is_event) wait_all();
    }

    //! Wait for any event occuring either on the display \c disp1 or \c disp2.
    static void wait(CImgDisplay& disp1, CImgDisplay& disp2) {
      disp1.is_event = disp2.is_event = 0;
      while (!disp1.is_event && !disp2.is_event) wait_all();
    }

    //! Wait for any event occuring either on the display \c disp1, \c disp2 or \c disp3.
    static void wait(CImgDisplay& disp1, CImgDisplay& disp2, CImgDisplay& disp3) {
      disp1.is_event = disp2.is_event = disp3.is_event = 0;
      while (!disp1.is_event && !disp2.is_event && !disp3.is_event) wait_all();
    }

    //! Wait for any event occuring either on the display \c disp1, \c disp2, \c disp3 or \c disp4.
    static void wait(CImgDisplay& disp1, CImgDisplay& disp2, CImgDisplay& disp3, CImgDisplay& disp4) {
      disp1.is_event = disp2.is_event = disp3.is_event = disp4.is_event = 0;
      while (!disp1.is_event && !disp2.is_event && !disp3.is_event && !disp4.is_event) wait_all();
    }

    //! Return the frame per second rate.
    float frames_per_second() {
      if (!fps_timer) fps_timer = cimg::time();
      const float delta = (cimg::time()-fps_timer)/1000.0f;
      ++fps_frames;
      if (delta>=1) {
        fps_fps = fps_frames/delta;
        fps_frames = 0;
        fps_timer = cimg::time();
      }
      return fps_fps;
    }

    //! Display an image list CImgList<T> into a display window.
    /** First, all images of the list are appended into a single image used for visualization,
        then this image is displayed in the current display window.
        \param list     : The list of images to display.
        \param axis     : The axis used to append the image for visualization. Can be 'x' (default),'y','z' or 'v'.
        \param align : Defines the relative alignment of images when displaying images of different sizes.
        Can be '\p c' (centered, which is the default), '\p p' (top alignment) and '\p n' (bottom aligment).
    **/
    template<typename T>
    CImgDisplay& display(const CImgList<T>& list, const char axis='x', const char align='p') {
      return display(list.get_append(axis,align));
    }

    //! Display an image CImg<T> into a display window.
    template<typename T>
    CImgDisplay& operator<<(const CImg<T>& img) {
      return display(img);
    }

    //! Display an image CImg<T> into a display window.
    template<typename T>
    CImgDisplay& operator<<(const CImgList<T>& list) {
      return display(list);
    }

    //! Resize a display window with the size of an image.
    /** \param img    : Input image. \p image.width and \p image.height give the new dimensions of the display window.
        \param redraw : If \p true (default), the current displayed image in the display window will
        be bloc-interpolated to fit the new dimensions. If \p false, a black image will be drawn in the resized window.
    **/
    template<typename T>
    CImgDisplay& resize(const CImg<T>& img, const bool redraw=true) {
      return resize(img.width,img.height,redraw);
    }

    //! Resize a display window using the size of the given display \p disp.
    CImgDisplay& resize(const CImgDisplay& disp, const bool redraw=true) {
      return resize(disp.width,disp.height,redraw);
    }

    //! Resize a display window in its current size.
    CImgDisplay& resize(const bool redraw=true) {
      resize(window_width,window_height,redraw);
      return *this;
    }

    //! Set fullscreen mode.
    CImgDisplay& fullscreen(const bool redraw=true) {
      if (is_empty() || is_fullscreen) return *this;
      return toggle_fullscreen(redraw);
    }

    //! Set normal screen mode.
    CImgDisplay& normalscreen(const bool redraw=true) {
      if (is_empty() || !is_fullscreen) return *this;
      return toggle_fullscreen(redraw);
    }

    // Inner routine used for fast resizing of buffer to display size.
    template<typename t, typename T>
    static void _render_resize(const T *ptrs, const unsigned int ws, const unsigned int hs,
                               t *ptrd, const unsigned int wd, const unsigned int hd) {
      unsigned int *const offx = new unsigned int[wd], *const offy = new unsigned int[hd+1], *poffx, *poffy;
      float s, curr, old;
      s = (float)ws/wd;
      poffx = offx; curr = 0; for (unsigned int x=0; x<wd; ++x) { old=curr; curr+=s; *(poffx++) = (unsigned int)curr-(unsigned int)old; }
      s = (float)hs/hd;
      poffy = offy; curr = 0; for (unsigned int y=0; y<hd; ++y) { old=curr; curr+=s; *(poffy++) = ws*((unsigned int)curr-(unsigned int)old); }
      *poffy = 0;
      poffy = offy;
      {for (unsigned int y=0; y<hd; ) {
        const T *ptr = ptrs;
        poffx = offx;
        for (unsigned int x=0; x<wd; ++x) { *(ptrd++) = *ptr; ptr+=*(poffx++); }
        ++y;
        unsigned int dy=*(poffy++);
        for (;!dy && y<hd; cimg_std::memcpy(ptrd, ptrd-wd, sizeof(t)*wd), ++y, ptrd+=wd, dy=*(poffy++)) {}
        ptrs+=dy;
      }}
      delete[] offx; delete[] offy;
    }

    //! Clear all events of the current display.
    CImgDisplay& flush() {
      cimg_std::memset((void*)buttons,0,512*sizeof(unsigned int));
      cimg_std::memset((void*)keys,0,512*sizeof(unsigned int));
      cimg_std::memset((void*)released_keys,0,512*sizeof(unsigned int));
      is_keyESC = is_keyF1 = is_keyF2 = is_keyF3 = is_keyF4 = is_keyF5 = is_keyF6 = is_keyF7 = is_keyF8 = is_keyF9 =
        is_keyF10 = is_keyF11 = is_keyF12 = is_keyPAUSE = is_key1 = is_key2 = is_key3 = is_key4 = is_key5 = is_key6 =
        is_key7 = is_key8 = is_key9 = is_key0 = is_keyBACKSPACE = is_keyINSERT = is_keyHOME = is_keyPAGEUP = is_keyTAB =
        is_keyQ = is_keyW = is_keyE = is_keyR = is_keyT = is_keyY = is_keyU = is_keyI = is_keyO = is_keyP = is_keyDELETE =
        is_keyEND = is_keyPAGEDOWN = is_keyCAPSLOCK = is_keyA = is_keyS = is_keyD = is_keyF = is_keyG = is_keyH = is_keyJ =
        is_keyK = is_keyL = is_keyENTER = is_keySHIFTLEFT = is_keyZ = is_keyX = is_keyC = is_keyV = is_keyB = is_keyN =
        is_keyM = is_keySHIFTRIGHT = is_keyARROWUP = is_keyCTRLLEFT = is_keyAPPLEFT = is_keyALT = is_keySPACE = is_keyALTGR = is_keyAPPRIGHT =
        is_keyMENU = is_keyCTRLRIGHT = is_keyARROWLEFT = is_keyARROWDOWN = is_keyARROWRIGHT = is_keyPAD0 = is_keyPAD1 = is_keyPAD2 =
        is_keyPAD3 = is_keyPAD4 = is_keyPAD5 = is_keyPAD6 = is_keyPAD7 = is_keyPAD8 = is_keyPAD9 = is_keyPADADD = is_keyPADSUB =
        is_keyPADMUL = is_keyPADDIV = false;
      is_resized = is_moved = is_event = false;
      fps_timer = fps_frames = timer = wheel = 0;
      mouse_x = mouse_y = -1;
      fps_fps = 0;
      return *this;
    }

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

    //! Test if any key has been pressed.
    bool is_key(const bool remove=false) {
      for (unsigned int *ptrs=(unsigned int*)keys+512-1; ptrs>=keys; --ptrs) if (*ptrs) { if (remove) *ptrs = 0; return true; }
      return false;
    }

    //! Test if a key has been pressed.
    bool is_key(const unsigned int key1, const bool remove) {
      for (unsigned int *ptrs=(unsigned int*)keys+512-1; ptrs>=keys; --ptrs) if (*ptrs==key1) { if (remove) *ptrs = 0; return true; }
      return false;
    }

    //! Test if a key sequence has been typed.
    bool is_key(const unsigned int key1, const unsigned int key2, const bool remove) {
      const unsigned int seq[] = { key1, key2 };
      return is_key(seq,2,remove);
    }

    //! Test if a key sequence has been typed.
    bool is_key(const unsigned int key1, const unsigned int key2, const unsigned int key3, const bool remove) {
      const unsigned int seq[] = { key1, key2, key3 };
      return is_key(seq,3,remove);
    }

    //! Test if a key sequence has been typed.
    bool is_key(const unsigned int key1, const unsigned int key2, const unsigned int key3,
                 const unsigned int key4, const bool remove) {
      const unsigned int seq[] = { key1, key2, key3, key4 };
      return is_key(seq,4,remove);
    }

    //! Test if a key sequence has been typed.
    bool is_key(const unsigned int key1, const unsigned int key2, const unsigned int key3,
                const unsigned int key4, const unsigned int key5, const bool remove) {
      const unsigned int seq[] = { key1, key2, key3, key4, key5 };
      return is_key(seq,5,remove);
    }

    //! Test if a key sequence has been typed.
    bool is_key(const unsigned int key1, const unsigned int key2, const unsigned int key3,
                const unsigned int key4, const unsigned int key5, const unsigned int key6, const bool remove) {
      const unsigned int seq[] = { key1, key2, key3, key4, key5, key6 };
      return is_key(seq,6,remove);
    }

    //! Test if a key sequence has been typed.
    bool is_key(const unsigned int key1, const unsigned int key2, const unsigned int key3,
                const unsigned int key4, const unsigned int key5, const unsigned int key6,
                const unsigned int key7, const bool remove) {
      const unsigned int seq[] = { key1, key2, key3, key4, key5, key6, key7 };
      return is_key(seq,7,remove);
    }

    //! Test if a key sequence has been typed.
    bool is_key(const unsigned int key1, const unsigned int key2, const unsigned int key3,
                const unsigned int key4, const unsigned int key5, const unsigned int key6,
                const unsigned int key7, const unsigned int key8, const bool remove) {
      const unsigned int seq[] = { key1, key2, key3, key4, key5, key6, key7, key8 };
      return is_key(seq,8,remove);
    }

    //! Test if a key sequence has been typed.
    bool is_key(const unsigned int key1, const unsigned int key2, const unsigned int key3,
                const unsigned int key4, const unsigned int key5, const unsigned int key6,
                const unsigned int key7, const unsigned int key8, const unsigned int key9, const bool remove) {
      const unsigned int seq[] = { key1, key2, key3, key4, key5, key6, key7, key8, key9 };
      return is_key(seq,9,remove);
    }

    //! Test if a key sequence has been typed.
    bool is_key(const unsigned int *const keyseq, const unsigned int N, const bool remove=true) {
      if (keyseq && N) {
        const unsigned int *const ps_end = keyseq+N-1, k = *ps_end, *const pk_end = (unsigned int*)keys+1+512-N;
        for (unsigned int *pk = (unsigned int*)keys; pk<pk_end; ) {
          if (*(pk++)==k) {
            bool res = true;
            const unsigned int *ps = ps_end, *pk2 = pk;
            for (unsigned int i=1; i<N; ++i) res = (*(--ps)==*(pk2++));
            if (res) {
              if (remove) cimg_std::memset((void*)(pk-1),0,sizeof(unsigned int)*N);
              return true;
            }
          }
        }
      }
      return false;
    }

    // Find the good width and height of a window to display an image (internal routine).
#define cimg_fitscreen(dx,dy,dz) CImgDisplay::_fitscreen(dx,dy,dz,128,-85,false),CImgDisplay::_fitscreen(dx,dy,dz,128,-85,true)
    static unsigned int _fitscreen(const unsigned int dx, const unsigned int dy=1, const unsigned int dz=1,
                                   const int dmin=128, const int dmax=-85,const bool return_last=false) {
      unsigned int nw = dx + (dz>1?dz:0), nh = dy + (dz>1?dz:0);
      const unsigned int
        sw = CImgDisplay::screen_dimx(), sh = CImgDisplay::screen_dimy(),
        mw = dmin<0?(unsigned int)(sw*-dmin/100):(unsigned int)dmin,
        mh = dmin<0?(unsigned int)(sh*-dmin/100):(unsigned int)dmin,
        Mw = dmax<0?(unsigned int)(sw*-dmax/100):(unsigned int)dmax,
        Mh = dmax<0?(unsigned int)(sh*-dmax/100):(unsigned int)dmax;
      if (nw<mw) { nh = nh*mw/nw; nh+=(nh==0); nw = mw; }
      if (nh<mh) { nw = nw*mh/nh; nw+=(nw==0); nh = mh; }
      if (nw>Mw) { nh = nh*Mw/nw; nh+=(nh==0); nw = Mw; }
      if (nh>Mh) { nw = nw*Mh/nh; nw+=(nw==0); nh = Mh; }
      if (nw<mw) nw = mw;
      if (nh<mh) nh = mh;
      if (return_last) return nh;
      return nw;
    }

    // When no display available
    //---------------------------
#if cimg_display==0

    //! Return the width of the screen resolution.
    static int screen_dimx() {
      return 0;
    }

    //! Return the height of the screen resolution.
    static int screen_dimy() {
      return 0;
    }

    //! Wait for a window event in any CImg window.
    static void wait_all() {}

    //! In-place version of the destructor.
    CImgDisplay& assign() {
      return *this;
    }

    //! In-place version of the previous constructor.
    CImgDisplay& assign(const unsigned int dimw, const unsigned int dimh, const char *title=0,
                        const unsigned int normalization_type=3,
                        const bool fullscreen_flag=false, const bool closed_flag=false) {
      throw CImgDisplayException("CImgDisplay() : Display has been required but is not available (cimg_display=0)");
      const char* avoid_warning = title + dimw + dimh + normalization_type + (int)fullscreen_flag + (int)closed_flag;
      (void)avoid_warning;
      return *this;
    }

    //! In-place version of the previous constructor.
    template<typename T>
    CImgDisplay& assign(const CImg<T>& img, const char *title=0,
                        const unsigned int normalization_type=3,
                        const bool fullscreen_flag=false, const bool closed_flag=false) {
      throw CImgDisplayException("CImgDisplay()::assign() : Display has been required but is not available (cimg_display=0)");
      const char* avoid_warning = title + img.width + normalization_type + (int)fullscreen_flag + (int)closed_flag;
      avoid_warning = 0;
      return assign(0,0);
    }

    //! In-place version of the previous constructor.
    template<typename T>
    CImgDisplay& assign(const CImgList<T>& list, const char *title=0,
                        const unsigned int normalization_type=3,
                        const bool fullscreen_flag=false, const bool closed_flag=false) {
      throw CImgDisplayException("CImgDisplay()::assign() : Display has been required but is not available (cimg_display=0)");
      const char* avoid_warning = title + list.size + normalization_type + (int)fullscreen_flag + (int)closed_flag;
      (void)avoid_warning;
      return assign(0,0);
    }

    //! In-place version of the previous constructor.
    CImgDisplay& assign(const CImgDisplay &disp) {
      return assign(disp.width,disp.height);
    }

    //! Resize window.
    CImgDisplay& resize(const int width, const int height, const bool redraw=true) {
      int avoid_warning = width | height | (int)redraw;
      (void)avoid_warning;
      return *this;
    }

    //! Toggle fullscreen mode.
    CImgDisplay& toggle_fullscreen(const bool redraw=true) {
      bool avoid_warning = redraw;
      (void)avoid_warning;
      return *this;
    }

    //! Show a closed display.
    CImgDisplay& show() {
      return *this;
    }

    //! Close a visible display.
    CImgDisplay& close() {
      return *this;
    }

    //! Move window.
    CImgDisplay& move(const int posx, const int posy) {
      int avoid_warning = posx | posy;
      (void)avoid_warning;
      return *this;
    }

    //! Show mouse pointer.
    CImgDisplay& show_mouse() {
      return *this;
    }

    //! Hide mouse pointer.
    CImgDisplay& hide_mouse() {
      return *this;
    }

    //! Move mouse pointer to a specific location.
    CImgDisplay& set_mouse(const int posx, const int posy) {
      int avoid_warning = posx | posy;
      (void)avoid_warning;
      return *this;
    }

    //! Set the window title.
    CImgDisplay& set_title(const char *format, ...) {
      const char *avoid_warning = format;
      (void)avoid_warning;
      return *this;
    }

    //! Display an image in a window.
    template<typename T>
    CImgDisplay& display(const CImg<T>& img) {
      unsigned int avoid_warning = img.width;
      (void)avoid_warning;
      return *this;
    }

    //! Re-paint image content in window.
    CImgDisplay& paint() {
      return *this;
    }

    //! Render image buffer into GDI native image format.
    template<typename T>
    CImgDisplay& render(const CImg<T>& img) {
      unsigned int avoid_warning = img.width;
      (void)avoid_warning;
      return *this;
    }

    //! Take a snapshot of the display in the specified image.
    template<typename T>
    const CImgDisplay& snapshot(CImg<T>& img) const {
      img.assign(width,height,1,3,0);
      return *this;
    }

    // X11-based display
    //-------------------
#elif cimg_display==1
    Atom wm_delete_window, wm_delete_protocol;
    Window window, background_window;
    Colormap colormap;
    XImage *image;
    void *data;
#ifdef cimg_use_xshm
    XShmSegmentInfo *shminfo;
#endif

    static int screen_dimx() {
      int res = 0;
      if (!cimg::X11attr().display) {
        Display *disp = XOpenDisplay((cimg_std::getenv("DISPLAY")?cimg_std::getenv("DISPLAY"):":0.0"));
        if (!disp)
          throw CImgDisplayException("CImgDisplay::screen_dimx() : Can't open X11 display.");
        res = DisplayWidth(disp,DefaultScreen(disp));
        XCloseDisplay(disp);
      } else {
#ifdef cimg_use_xrandr
        if (cimg::X11attr().resolutions && cimg::X11attr().curr_resolution)
          res = cimg::X11attr().resolutions[cimg::X11attr().curr_resolution].width;
        else
#endif
          res = DisplayWidth(cimg::X11attr().display,DefaultScreen(cimg::X11attr().display));
      }
      return res;
    }

    static int screen_dimy() {
      int res = 0;
      if (!cimg::X11attr().display) {
        Display *disp = XOpenDisplay((cimg_std::getenv("DISPLAY") ? cimg_std::getenv("DISPLAY") : ":0.0"));
        if (!disp)
          throw CImgDisplayException("CImgDisplay::screen_dimy() : Can't open X11 display.");
        res = DisplayHeight(disp,DefaultScreen(disp));
        XCloseDisplay(disp);
      } else {
#ifdef cimg_use_xrandr
        if (cimg::X11attr().resolutions && cimg::X11attr().curr_resolution)
          res = cimg::X11attr().resolutions[cimg::X11attr().curr_resolution].height;
        else
#endif
          res = DisplayHeight(cimg::X11attr().display,DefaultScreen(cimg::X11attr().display));
      }
      return res;
    }

    static void wait_all() {
      if (cimg::X11attr().display) {
        XLockDisplay(cimg::X11attr().display);
        bool flag = true;
        XEvent event;
        while (flag) {
          XNextEvent(cimg::X11attr().display, &event);
          for (unsigned int i = 0; i<cimg::X11attr().nb_wins; ++i)
            if (!cimg::X11attr().wins[i]->is_closed && event.xany.window==cimg::X11attr().wins[i]->window) {
              cimg::X11attr().wins[i]->_handle_events(&event);
              if (cimg::X11attr().wins[i]->is_event) flag = false;
            }
        }
        XUnlockDisplay(cimg::X11attr().display);
      }
    }

    void _handle_events(const XEvent *const pevent) {
      XEvent event = *pevent;
      switch (event.type) {
      case ClientMessage : {
        if ((int)event.xclient.message_type==(int)wm_delete_protocol &&
            (int)event.xclient.data.l[0]==(int)wm_delete_window) {
          XUnmapWindow(cimg::X11attr().display,window);
          mouse_x = mouse_y = -1;
          if (button) { cimg_std::memmove((void*)(buttons+1),(void*)buttons,512-1); button = 0; }
          if (key) { cimg_std::memmove((void*)(keys+1),(void*)keys,512-1); key = 0; }
          if (released_key) { cimg_std::memmove((void*)(released_keys+1),(void*)released_keys,512-1); released_key = 0; }
          is_closed = is_event = true;
        }
      } break;
      case ConfigureNotify : {
        while (XCheckWindowEvent(cimg::X11attr().display,window,StructureNotifyMask,&event)) {}
        const unsigned int
          nw = event.xconfigure.width,
          nh = event.xconfigure.height;
        const int
          nx = event.xconfigure.x,
          ny = event.xconfigure.y;
        if (nw && nh && (nw!=window_width || nh!=window_height)) {
          window_width = nw;
          window_height = nh;
          mouse_x = mouse_y = -1;
          XResizeWindow(cimg::X11attr().display,window,window_width,window_height);
          is_resized = is_event = true;
        }
        if (nx!=window_x || ny!=window_y) {
          window_x = nx;
          window_y = ny;
          is_moved = is_event = true;
        }
      } break;
      case Expose : {
        while (XCheckWindowEvent(cimg::X11attr().display,window,ExposureMask,&event)) {}
        _paint(false);
        if (is_fullscreen) {
          XWindowAttributes attr;
          XGetWindowAttributes(cimg::X11attr().display, window, &attr);
          while (attr.map_state != IsViewable) XSync(cimg::X11attr().display, False);
          XSetInputFocus(cimg::X11attr().display, window, RevertToParent, CurrentTime);
        }
      } break;
      case ButtonPress : {
        do {
        mouse_x = event.xmotion.x;
        mouse_y = event.xmotion.y;
        if (mouse_x<0 || mouse_y<0 || mouse_x>=dimx() || mouse_y>=dimy()) mouse_x = mouse_y = -1;
          switch (event.xbutton.button) {
          case 1 : cimg_std::memmove((void*)(buttons+1),(void*)buttons,512-1); button|=1; is_event = true; break;
          case 2 : cimg_std::memmove((void*)(buttons+1),(void*)buttons,512-1); button|=4; is_event = true; break;
          case 3 : cimg_std::memmove((void*)(buttons+1),(void*)buttons,512-1); button|=2; is_event = true; break;
          }
        } while (XCheckWindowEvent(cimg::X11attr().display,window,ButtonPressMask,&event));
      } break;
      case ButtonRelease : {
        do {
        mouse_x = event.xmotion.x;
        mouse_y = event.xmotion.y;
        if (mouse_x<0 || mouse_y<0 || mouse_x>=dimx() || mouse_y>=dimy()) mouse_x = mouse_y = -1;
          switch (event.xbutton.button) {
          case 1 : cimg_std::memmove((void*)(buttons+1),(void*)buttons,512-1); button&=~1U; is_event = true; break;
          case 2 : cimg_std::memmove((void*)(buttons+1),(void*)buttons,512-1); button&=~4U; is_event = true; break;
          case 3 : cimg_std::memmove((void*)(buttons+1),(void*)buttons,512-1); button&=~2U; is_event = true; break;
          case 4 : ++wheel; is_event = true; break;
          case 5 : --wheel; is_event = true; break;
          }
        } while (XCheckWindowEvent(cimg::X11attr().display,window,ButtonReleaseMask,&event));
      } break;
      case KeyPress : {
        char tmp;
        KeySym ksym;
        XLookupString(&event.xkey,&tmp,1,&ksym,0);
        update_iskey((unsigned int)ksym,true);
        if (key) cimg_std::memmove((void*)(keys+1),(void*)keys,512-1);
        key = (unsigned int)ksym;
        if (released_key) { cimg_std::memmove((void*)(released_keys+1),(void*)released_keys,512-1); released_key = 0; }
        is_event = true;
      } break;
      case KeyRelease : {
        char tmp;
        KeySym ksym;
        XLookupString(&event.xkey,&tmp,1,&ksym,0);
        update_iskey((unsigned int)ksym,false);
        if (key) { cimg_std::memmove((void*)(keys+1),(void*)keys,512-1); key = 0; }
        if (released_key) cimg_std::memmove((void*)(released_keys+1),(void*)released_keys,512-1);
        released_key = (unsigned int)ksym;
        is_event = true;
      } break;
      case EnterNotify: {
        while (XCheckWindowEvent(cimg::X11attr().display,window,EnterWindowMask,&event)) {}
        mouse_x = event.xmotion.x;
        mouse_y = event.xmotion.y;
        if (mouse_x<0 || mouse_y<0 || mouse_x>=dimx() || mouse_y>=dimy()) mouse_x = mouse_y = -1;
      } break;
      case LeaveNotify : {
        while (XCheckWindowEvent(cimg::X11attr().display,window,LeaveWindowMask,&event)) {}
        mouse_x = mouse_y =-1;
        is_event = true;
      } break;
      case MotionNotify : {
        while (XCheckWindowEvent(cimg::X11attr().display,window,PointerMotionMask,&event)) {}
        mouse_x = event.xmotion.x;
        mouse_y = event.xmotion.y;
        if (mouse_x<0 || mouse_y<0 || mouse_x>=dimx() || mouse_y>=dimy()) mouse_x = mouse_y = -1;
        is_event = true;
      } break;
      }
    }

    static void* _events_thread(void *arg) {
      arg = 0;
      XEvent event;
      pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED,0);
      pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,0);
      for (;;) {
        XLockDisplay(cimg::X11attr().display);
        bool event_flag = XCheckTypedEvent(cimg::X11attr().display, ClientMessage, &event);
        if (!event_flag) event_flag = XCheckMaskEvent(cimg::X11attr().display,
                                                      ExposureMask|StructureNotifyMask|ButtonPressMask|
                                                      KeyPressMask|PointerMotionMask|EnterWindowMask|LeaveWindowMask|
                                                      ButtonReleaseMask|KeyReleaseMask,&event);
        if (event_flag) {
          for (unsigned int i=0; i<cimg::X11attr().nb_wins; ++i)
            if (!cimg::X11attr().wins[i]->is_closed && event.xany.window==cimg::X11attr().wins[i]->window)
              cimg::X11attr().wins[i]->_handle_events(&event);
        }
        XUnlockDisplay(cimg::X11attr().display);
        pthread_testcancel();
        cimg::sleep(7);
      }
      return 0;
    }

    void _set_colormap(Colormap& colormap, const unsigned int dim) {
      XColor palette[256];
      switch (dim) {
      case 1 : { // palette for greyscale images
        for (unsigned int index=0; index<256; ++index) {
          palette[index].pixel = index;
          palette[index].red = palette[index].green = palette[index].blue = (unsigned short)(index<<8);
          palette[index].flags = DoRed | DoGreen | DoBlue;
        }
      } break;
      case 2 : { // palette for RG images
        for (unsigned int index=0, r=8; r<256; r+=16)
          for (unsigned int g=8; g<256; g+=16) {
            palette[index].pixel = index;
            palette[index].red = palette[index].blue = (unsigned short)(r<<8);
            palette[index].green = (unsigned short)(g<<8);
            palette[index++].flags = DoRed | DoGreen | DoBlue;
          }
      } break;
      default : { // palette for RGB images
        for (unsigned int index=0, r=16; r<256; r+=32)
          for (unsigned int g=16; g<256; g+=32)
            for (unsigned int b=32; b<256; b+=64) {
              palette[index].pixel = index;
              palette[index].red = (unsigned short)(r<<8);
              palette[index].green = (unsigned short)(g<<8);
              palette[index].blue = (unsigned short)(b<<8);
              palette[index++].flags = DoRed | DoGreen | DoBlue;
            }
      }
      }
      XStoreColors(cimg::X11attr().display,colormap,palette,256);
    }

    void _map_window() {
      XWindowAttributes attr;
      XEvent event;
      bool exposed = false, mapped = false;
      XMapRaised(cimg::X11attr().display,window);
      XSync(cimg::X11attr().display,False);
      do {
        XWindowEvent(cimg::X11attr().display,window,StructureNotifyMask | ExposureMask,&event);
        switch (event.type) {
        case MapNotify : mapped = true; break;
        case Expose : exposed = true; break;
        default : XSync(cimg::X11attr().display, False); cimg::sleep(10);
        }
      } while (!(exposed && mapped));
      do {
        XGetWindowAttributes(cimg::X11attr().display, window, &attr);
        if (attr.map_state!=IsViewable) { XSync(cimg::X11attr().display,False); cimg::sleep(10); }
      } while (attr.map_state != IsViewable);
      window_x = attr.x;
      window_y = attr.y;
    }

    void _paint(const bool wait_expose=true) {
      if (!is_closed) {
        if (wait_expose) {
          static XEvent event;
          event.xexpose.type = Expose;
          event.xexpose.serial = 0;
          event.xexpose.send_event = True;
          event.xexpose.display = cimg::X11attr().display;
          event.xexpose.window = window;
          event.xexpose.x = 0;
          event.xexpose.y = 0;
          event.xexpose.width = dimx();
          event.xexpose.height = dimy();
          event.xexpose.count = 0;
          XSendEvent(cimg::X11attr().display, window, False, 0, &event);
        } else {
#ifdef cimg_use_xshm
          if (shminfo) XShmPutImage(cimg::X11attr().display,window,*cimg::X11attr().gc,image,0,0,0,0,width,height,False);
          else
#endif
            XPutImage(cimg::X11attr().display,window,*cimg::X11attr().gc,image,0,0,0,0,width,height);
          XSync(cimg::X11attr().display, False);
        }
      }
    }

    template<typename T>
    void _resize(T foo, const unsigned int ndimx, const unsigned int ndimy, const bool redraw) {
      foo = 0;
#ifdef cimg_use_xshm
      if (shminfo) {
        XShmSegmentInfo *nshminfo = new XShmSegmentInfo;
        XImage *nimage = XShmCreateImage(cimg::X11attr().display,DefaultVisual(cimg::X11attr().display,DefaultScreen(cimg::X11attr().display)),
                                         cimg::X11attr().nb_bits,ZPixmap,0,nshminfo,ndimx,ndimy);
        if (!nimage) {
          delete nshminfo;
          return;
        } else {
          nshminfo->shmid = shmget(IPC_PRIVATE, ndimx*ndimy*sizeof(T), IPC_CREAT | 0777);
          if (nshminfo->shmid==-1) {
            XDestroyImage(nimage);
            delete nshminfo;
            return;
          } else {
            nshminfo->shmaddr = nimage->data = (char*)shmat(nshminfo->shmid,0,0);
            if (nshminfo->shmaddr==(char*)-1) {
              shmctl(nshminfo->shmid,IPC_RMID,0);
              XDestroyImage(nimage);
              delete nshminfo;
              return;
            } else {
              nshminfo->readOnly = False;
              cimg::X11attr().shm_enabled = true;
              XErrorHandler oldXErrorHandler = XSetErrorHandler(_assign_xshm);
              XShmAttach(cimg::X11attr().display, nshminfo);
              XSync(cimg::X11attr().display, False);
              XSetErrorHandler(oldXErrorHandler);
              if (!cimg::X11attr().shm_enabled) {
                shmdt(nshminfo->shmaddr);
                shmctl(nshminfo->shmid,IPC_RMID,0);
                XDestroyImage(nimage);
                delete nshminfo;
                return;
              } else {
                T *const ndata = (T*)nimage->data;
                if (redraw) _render_resize((T*)data,width,height,ndata,ndimx,ndimy);
                else cimg_std::memset(ndata,0,sizeof(T)*ndimx*ndimy);
                XShmDetach(cimg::X11attr().display, shminfo);
                XDestroyImage(image);
                shmdt(shminfo->shmaddr);
                shmctl(shminfo->shmid,IPC_RMID,0);
                delete shminfo;
                shminfo = nshminfo;
                image = nimage;
                data = (void*)ndata;
              }
            }
          }
        }
      } else
#endif
        {
          T *ndata = (T*)cimg_std::malloc(ndimx*ndimy*sizeof(T));
          if (redraw) _render_resize((T*)data,width,height,ndata,ndimx,ndimy);
          else cimg_std::memset(ndata,0,sizeof(T)*ndimx*ndimy);
          data = (void*)ndata;
          XDestroyImage(image);
          image = XCreateImage(cimg::X11attr().display,DefaultVisual(cimg::X11attr().display,DefaultScreen(cimg::X11attr().display)),
                               cimg::X11attr().nb_bits,ZPixmap,0,(char*)data,ndimx,ndimy,8,0);
        }
    }

    void _init_fullscreen() {
      background_window = 0;
      if (is_fullscreen && !is_closed) {
#ifdef cimg_use_xrandr
        int foo;
        if (XRRQueryExtension(cimg::X11attr().display,&foo,&foo)) {
          XRRRotations(cimg::X11attr().display, DefaultScreen(cimg::X11attr().display), &cimg::X11attr().curr_rotation);
          if (!cimg::X11attr().resolutions) {
            cimg::X11attr().resolutions = XRRSizes(cimg::X11attr().display,DefaultScreen(cimg::X11attr().display),&foo);
            cimg::X11attr().nb_resolutions = (unsigned int)foo;
          }
          if (cimg::X11attr().resolutions) {
            cimg::X11attr().curr_resolution = 0;
            for (unsigned int i=0; i<cimg::X11attr().nb_resolutions; ++i) {
              const unsigned int
                nw = (unsigned int)(cimg::X11attr().resolutions[i].width),
                nh = (unsigned int)(cimg::X11attr().resolutions[i].height);
              if (nw>=width && nh>=height &&
                  nw<=(unsigned int)(cimg::X11attr().resolutions[cimg::X11attr().curr_resolution].width) &&
                  nh<=(unsigned int)(cimg::X11attr().resolutions[cimg::X11attr().curr_resolution].height))
                cimg::X11attr().curr_resolution = i;
            }
            if (cimg::X11attr().curr_resolution>0) {
              XRRScreenConfiguration *config = XRRGetScreenInfo(cimg::X11attr().display, DefaultRootWindow(cimg::X11attr().display));
              XRRSetScreenConfig(cimg::X11attr().display, config, DefaultRootWindow(cimg::X11attr().display),
                                 cimg::X11attr().curr_resolution, cimg::X11attr().curr_rotation, CurrentTime);
              XRRFreeScreenConfigInfo(config);
              XSync(cimg::X11attr().display, False);
            }
          }
        }
        if (!cimg::X11attr().resolutions)
          cimg::warn("CImgDisplay::_create_window() : Xrandr extension is not supported by the X server.");
#endif
        const unsigned int sx = screen_dimx(), sy = screen_dimy();
        XSetWindowAttributes winattr;
        winattr.override_redirect = True;
        if (sx!=width || sy!=height) {
          background_window = XCreateWindow(cimg::X11attr().display,
                                            RootWindow(cimg::X11attr().display,DefaultScreen(cimg::X11attr().display)),0,0,
                                            sx,sy,0,0,InputOutput,CopyFromParent,CWOverrideRedirect,&winattr);
          const unsigned int bufsize = sx*sy*(cimg::X11attr().nb_bits==8?1:(cimg::X11attr().nb_bits==16?2:4));
          void *background_data = cimg_std::malloc(bufsize);
          cimg_std::memset(background_data,0,bufsize);
          XImage *background_image = XCreateImage(cimg::X11attr().display,DefaultVisual(cimg::X11attr().display,DefaultScreen(cimg::X11attr().display)),
                                                  cimg::X11attr().nb_bits,ZPixmap,0,(char*)background_data,sx,sy,8,0);
          XEvent event;
          XSelectInput(cimg::X11attr().display,background_window,StructureNotifyMask);
          XMapRaised(cimg::X11attr().display,background_window);
          do XWindowEvent(cimg::X11attr().display,background_window,StructureNotifyMask,&event);
          while (event.type!=MapNotify);
#ifdef cimg_use_xshm
          if (shminfo) XShmPutImage(cimg::X11attr().display,background_window,*cimg::X11attr().gc,background_image,0,0,0,0,sx,sy,False);
          else
#endif
            XPutImage(cimg::X11attr().display,background_window,*cimg::X11attr().gc,background_image,0,0,0,0,sx,sy);
          XWindowAttributes attr;
          XGetWindowAttributes(cimg::X11attr().display, background_window, &attr);
          while (attr.map_state != IsViewable) XSync(cimg::X11attr().display, False);
          XDestroyImage(background_image);
        }
      }
    }

    void _desinit_fullscreen() {
      if (is_fullscreen) {
        XUngrabKeyboard(cimg::X11attr().display,CurrentTime);
#ifdef cimg_use_xrandr
        if (cimg::X11attr().resolutions && cimg::X11attr().curr_resolution) {
          XRRScreenConfiguration *config = XRRGetScreenInfo(cimg::X11attr().display, DefaultRootWindow(cimg::X11attr().display));
          XRRSetScreenConfig(cimg::X11attr().display, config, DefaultRootWindow(cimg::X11attr().display),
                             0, cimg::X11attr().curr_rotation, CurrentTime);
          XRRFreeScreenConfigInfo(config);
          XSync(cimg::X11attr().display, False);
          cimg::X11attr().curr_resolution = 0;
        }
#endif
        if (background_window) XDestroyWindow(cimg::X11attr().display,background_window);
        background_window = 0;
        is_fullscreen = false;
      }
    }

    static int _assign_xshm(Display *dpy, XErrorEvent *error) {
      dpy = 0; error = 0;
      cimg::X11attr().shm_enabled = false;
      return 0;
    }

    void _assign(const unsigned int dimw, const unsigned int dimh, const char *ptitle=0,
                 const unsigned int normalization_type=3,
                 const bool fullscreen_flag=false, const bool closed_flag=false) {

      // Allocate space for window title
      const int s = cimg::strlen(ptitle)+1;
      char *tmp_title = s?new char[s]:0;
      if (s) cimg_std::memcpy(tmp_title,ptitle,s*sizeof(char));

      // Destroy previous display window if existing
      if (!is_empty()) assign();

      // Open X11 display if necessary.
      if (!cimg::X11attr().display) {
        static bool xinit_threads = false;
        if (!xinit_threads) { XInitThreads(); xinit_threads = true; }
        cimg::X11attr().nb_wins = 0;
        cimg::X11attr().display = XOpenDisplay((cimg_std::getenv("DISPLAY")?cimg_std::getenv("DISPLAY"):":0.0"));
        if (!cimg::X11attr().display)
          throw CImgDisplayException("CImgDisplay::_create_window() : Can't open X11 display");
        cimg::X11attr().nb_bits = DefaultDepth(cimg::X11attr().display, DefaultScreen(cimg::X11attr().display));
        if (cimg::X11attr().nb_bits!=8 && cimg::X11attr().nb_bits!=16 && cimg::X11attr().nb_bits!=24 && cimg::X11attr().nb_bits!=32)
          throw CImgDisplayException("CImgDisplay::_create_window() : %u bits mode is not supported "
                                     "(only 8, 16, 24 and 32 bits modes are supported)",cimg::X11attr().nb_bits);
        cimg::X11attr().gc = new GC;
        *cimg::X11attr().gc = DefaultGC(cimg::X11attr().display,DefaultScreen(cimg::X11attr().display));
        Visual *visual = DefaultVisual(cimg::X11attr().display,DefaultScreen(cimg::X11attr().display));
        XVisualInfo vtemplate;
        vtemplate.visualid = XVisualIDFromVisual(visual);
        int nb_visuals;
        XVisualInfo *vinfo = XGetVisualInfo(cimg::X11attr().display,VisualIDMask,&vtemplate,&nb_visuals);
        if (vinfo && vinfo->red_mask<vinfo->blue_mask) cimg::X11attr().blue_first = true;
        cimg::X11attr().byte_order = ImageByteOrder(cimg::X11attr().display);
        XFree(vinfo);
        XLockDisplay(cimg::X11attr().display);
        cimg::X11attr().event_thread = new pthread_t;
        pthread_create(cimg::X11attr().event_thread,0,_events_thread,0);
      } else XLockDisplay(cimg::X11attr().display);

      // Set display variables
      width = cimg::min(dimw,(unsigned int)screen_dimx());
      height = cimg::min(dimh,(unsigned int)screen_dimy());
      normalization = normalization_type<4?normalization_type:3;
      is_fullscreen = fullscreen_flag;
      window_x = window_y = 0;
      is_closed = closed_flag;
      title = tmp_title;
      flush();

      // Create X11 window and palette (if 8bits display)
      if (is_fullscreen) {
        if (!is_closed) _init_fullscreen();
        const unsigned int sx = screen_dimx(), sy = screen_dimy();
        XSetWindowAttributes winattr;
        winattr.override_redirect = True;
        window = XCreateWindow(cimg::X11attr().display,
                               RootWindow(cimg::X11attr().display,DefaultScreen(cimg::X11attr().display)),
                               (sx-width)/2,(sy-height)/2,
                               width,height,0,0,InputOutput,CopyFromParent,CWOverrideRedirect,&winattr);
      } else
        window = XCreateSimpleWindow(cimg::X11attr().display,
                                     RootWindow(cimg::X11attr().display,DefaultScreen(cimg::X11attr().display)),
                                     0,0,width,height,2,0,0x0L);
      XStoreName(cimg::X11attr().display,window,title?title:" ");
      if (cimg::X11attr().nb_bits==8) {
        colormap = XCreateColormap(cimg::X11attr().display,window,DefaultVisual(cimg::X11attr().display,
                                                                                DefaultScreen(cimg::X11attr().display)),AllocAll);
        _set_colormap(colormap,3);
        XSetWindowColormap(cimg::X11attr().display,window,colormap);
      }
      window_width = width;
      window_height = height;

      // Create XImage
      const unsigned int bufsize = width*height*(cimg::X11attr().nb_bits==8?1:(cimg::X11attr().nb_bits==16?2:4));
#ifdef cimg_use_xshm
      shminfo = 0;
      if (XShmQueryExtension(cimg::X11attr().display)) {
        shminfo = new XShmSegmentInfo;
        image = XShmCreateImage(cimg::X11attr().display,DefaultVisual(cimg::X11attr().display,DefaultScreen(cimg::X11attr().display)),
                                cimg::X11attr().nb_bits,ZPixmap,0,shminfo,width,height);
        if (!image) {
          delete shminfo;
          shminfo = 0;
        } else {
          shminfo->shmid = shmget(IPC_PRIVATE, bufsize, IPC_CREAT | 0777);
          if (shminfo->shmid==-1) {
            XDestroyImage(image);
            delete shminfo;
            shminfo = 0;
          } else {
            shminfo->shmaddr = image->data = (char*)(data = shmat(shminfo->shmid,0,0));
            if (shminfo->shmaddr==(char*)-1) {
              shmctl(shminfo->shmid,IPC_RMID,0);
              XDestroyImage(image);
              delete shminfo;
              shminfo = 0;
            } else {
              shminfo->readOnly = False;
              cimg::X11attr().shm_enabled = true;
              XErrorHandler oldXErrorHandler = XSetErrorHandler(_assign_xshm);
              XShmAttach(cimg::X11attr().display, shminfo);
              XSync(cimg::X11attr().display, False);
              XSetErrorHandler(oldXErrorHandler);
              if (!cimg::X11attr().shm_enabled) {
                shmdt(shminfo->shmaddr);
                shmctl(shminfo->shmid,IPC_RMID,0);
                XDestroyImage(image);
                delete shminfo;
                shminfo = 0;
              }
            }
          }
        }
      }
      if (!shminfo)
#endif
        {
          data = cimg_std::malloc(bufsize);
          image = XCreateImage(cimg::X11attr().display,DefaultVisual(cimg::X11attr().display,DefaultScreen(cimg::X11attr().display)),
                               cimg::X11attr().nb_bits,ZPixmap,0,(char*)data,width,height,8,0);
        }

      wm_delete_window = XInternAtom(cimg::X11attr().display, "WM_DELETE_WINDOW", False);
      wm_delete_protocol = XInternAtom(cimg::X11attr().display, "WM_PROTOCOLS", False);
      XSetWMProtocols(cimg::X11attr().display, window, &wm_delete_window, 1);
      XSelectInput(cimg::X11attr().display,window,
                   ExposureMask | StructureNotifyMask | ButtonPressMask | KeyPressMask | PointerMotionMask |
                   EnterWindowMask | LeaveWindowMask | ButtonReleaseMask | KeyReleaseMask);
      if (is_fullscreen) XGrabKeyboard(cimg::X11attr().display, window, True, GrabModeAsync, GrabModeAsync, CurrentTime);
      cimg::X11attr().wins[cimg::X11attr().nb_wins++]=this;
      if (!is_closed) _map_window(); else { window_x = window_y = cimg::type<int>::min(); }
      XUnlockDisplay(cimg::X11attr().display);
    }

    CImgDisplay& assign() {
      if (is_empty()) return *this;
      XLockDisplay(cimg::X11attr().display);

      // Remove display window from event thread list.
      unsigned int i;
      for (i = 0; i<cimg::X11attr().nb_wins && cimg::X11attr().wins[i]!=this; ++i) {}
      for (; i<cimg::X11attr().nb_wins-1; ++i) cimg::X11attr().wins[i] = cimg::X11attr().wins[i+1];
      --cimg::X11attr().nb_wins;

      // Destroy window, image, colormap and title.
      if (is_fullscreen && !is_closed) _desinit_fullscreen();
      XDestroyWindow(cimg::X11attr().display,window);
      window = 0;
#ifdef cimg_use_xshm
      if (shminfo) {
        XShmDetach(cimg::X11attr().display, shminfo);
        XDestroyImage(image);
        shmdt(shminfo->shmaddr);
        shmctl(shminfo->shmid,IPC_RMID,0);
        delete shminfo;
        shminfo = 0;
      } else
#endif
        XDestroyImage(image);
      data = 0; image = 0;
      if (cimg::X11attr().nb_bits==8) XFreeColormap(cimg::X11attr().display,colormap);
      colormap = 0;
      XSync(cimg::X11attr().display, False);

      // Reset display variables
      if (title) delete[] title;
      width = height = normalization = window_width = window_height = 0;
      window_x = window_y = 0;
      is_fullscreen = false;
      is_closed = true;
      min = max = 0;
      title = 0;
      flush();

      // End event thread and close display if necessary
      XUnlockDisplay(cimg::X11attr().display);

      /* The code below was used to close the X11 display when not used anymore,
         unfortunately, since the latest Xorg versions, it randomely hangs, so
         I prefer to remove it. A fix would be needed anyway.

         if (!cimg::X11attr().nb_wins) {
         // Kill event thread
         pthread_cancel(*cimg::X11attr().event_thread);
         XUnlockDisplay(cimg::X11attr().display);
         pthread_join(*cimg::X11attr().event_thread,0);
         delete cimg::X11attr().event_thread;
         cimg::X11attr().event_thread = 0;
         XCloseDisplay(cimg::X11attr().display);
         cimg::X11attr().display = 0;
         delete cimg::X11attr().gc;
         cimg::X11attr().gc = 0;
         } else XUnlockDisplay(cimg::X11attr().display);
      */
      return *this;
    }

    CImgDisplay& assign(const unsigned int dimw, const unsigned int dimh, const char *title=0,
                        const unsigned int normalization_type=3,
                        const bool fullscreen_flag=false, const bool closed_flag=false) {
      if (!dimw || !dimh) return assign();
      _assign(dimw,dimh,title,normalization_type,fullscreen_flag,closed_flag);
      min = max = 0;
      cimg_std::memset(data,0,(cimg::X11attr().nb_bits==8?sizeof(unsigned char):
                          (cimg::X11attr().nb_bits==16?sizeof(unsigned short):sizeof(unsigned int)))*width*height);
      return paint();
    }

    template<typename T>
    CImgDisplay& assign(const CImg<T>& img, const char *title=0,
                        const unsigned int normalization_type=3,
                        const bool fullscreen_flag=false, const bool closed_flag=false) {
      if (!img) return assign();
      CImg<T> tmp;
      const CImg<T>& nimg = (img.depth==1)?img:(tmp=img.get_projections2d(img.width/2,img.height/2,img.depth/2));
      _assign(nimg.width,nimg.height,title,normalization_type,fullscreen_flag,closed_flag);
      if (normalization==2) min = (float)nimg.minmax(max);
      return render(nimg).paint();
    }

    template<typename T>
    CImgDisplay& assign(const CImgList<T>& list, const char *title=0,
                        const unsigned int normalization_type=3,
                        const bool fullscreen_flag=false, const bool closed_flag=false) {
      if (!list) return assign();
      CImg<T> tmp;
      const CImg<T> img = list.get_append('x','p'),
        &nimg = (img.depth==1)?img:(tmp=img.get_projections2d(img.width/2,img.height/2,img.depth/2));
      _assign(nimg.width,nimg.height,title,normalization_type,fullscreen_flag,closed_flag);
      if (normalization==2) min = (float)nimg.minmax(max);
      return render(nimg).paint();
    }

    CImgDisplay& assign(const CImgDisplay& win) {
      if (!win) return assign();
      _assign(win.width,win.height,win.title,win.normalization,win.is_fullscreen,win.is_closed);
      cimg_std::memcpy(data,win.data,(cimg::X11attr().nb_bits==8?sizeof(unsigned char):
                                 cimg::X11attr().nb_bits==16?sizeof(unsigned short):
                                 sizeof(unsigned int))*width*height);
      return paint();
    }

    CImgDisplay& resize(const int nwidth, const int nheight, const bool redraw=true) {
      if (!nwidth || !nheight || (is_empty() && (nwidth<0 || nheight<0))) return assign();
      if (is_empty()) return assign(nwidth,nheight);
      const unsigned int
        tmpdimx = (nwidth>0)?nwidth:(-nwidth*width/100),
        tmpdimy = (nheight>0)?nheight:(-nheight*height/100),
        dimx = tmpdimx?tmpdimx:1,
        dimy = tmpdimy?tmpdimy:1;
      XLockDisplay(cimg::X11attr().display);
      if (window_width!=dimx || window_height!=dimy) XResizeWindow(cimg::X11attr().display,window,dimx,dimy);
      if (width!=dimx || height!=dimy) switch (cimg::X11attr().nb_bits) {
      case 8 :  { unsigned char foo = 0; _resize(foo,dimx,dimy,redraw); } break;
      case 16 : { unsigned short foo = 0; _resize(foo,dimx,dimy,redraw); } break;
      default : { unsigned int foo = 0; _resize(foo,dimx,dimy,redraw); }
      }
      window_width = width = dimx; window_height = height = dimy;
      is_resized = false;
      XUnlockDisplay(cimg::X11attr().display);
      if (is_fullscreen) move((screen_dimx()-width)/2,(screen_dimy()-height)/2);
      if (redraw) return paint();
      return *this;
    }

    CImgDisplay& toggle_fullscreen(const bool redraw=true) {
      if (is_empty()) return *this;
      if (redraw) {
        const unsigned int bufsize = width*height*(cimg::X11attr().nb_bits==8?1:(cimg::X11attr().nb_bits==16?2:4));
        void *odata = cimg_std::malloc(bufsize);
        cimg_std::memcpy(odata,data,bufsize);
        assign(width,height,title,normalization,!is_fullscreen,false);
        cimg_std::memcpy(data,odata,bufsize);
        cimg_std::free(odata);
        return paint(false);
      }
      return assign(width,height,title,normalization,!is_fullscreen,false);
    }

    CImgDisplay& show() {
      if (!is_empty() && is_closed) {
        XLockDisplay(cimg::X11attr().display);
        if (is_fullscreen) _init_fullscreen();
        _map_window();
        is_closed = false;
        XUnlockDisplay(cimg::X11attr().display);
        return paint();
      }
      return *this;
    }

    CImgDisplay& close() {
      if (!is_empty() && !is_closed) {
        XLockDisplay(cimg::X11attr().display);
        if (is_fullscreen) _desinit_fullscreen();
        XUnmapWindow(cimg::X11attr().display,window);
        window_x = window_y = -1;
        is_closed = true;
        XUnlockDisplay(cimg::X11attr().display);
      }
      return *this;
    }

    CImgDisplay& move(const int posx, const int posy) {
      if (is_empty()) return *this;
      show();
      XLockDisplay(cimg::X11attr().display);
      XMoveWindow(cimg::X11attr().display,window,posx,posy);
      window_x = posx; window_y = posy;
      is_moved = false;
      XUnlockDisplay(cimg::X11attr().display);
      return paint();
    }

    CImgDisplay& show_mouse() {
      if (is_empty()) return *this;
      XLockDisplay(cimg::X11attr().display);
      XDefineCursor(cimg::X11attr().display,window,None);
      XUnlockDisplay(cimg::X11attr().display);
      return *this;
    }

    CImgDisplay& hide_mouse() {
      if (is_empty()) return *this;
      XLockDisplay(cimg::X11attr().display);
      const char pix_data[8] = { 0 };
      XColor col;
      col.red = col.green = col.blue = 0;
      Pixmap pix = XCreateBitmapFromData(cimg::X11attr().display,window,pix_data,8,8);
      Cursor cur = XCreatePixmapCursor(cimg::X11attr().display,pix,pix,&col,&col,0,0);
      XFreePixmap(cimg::X11attr().display,pix);
      XDefineCursor(cimg::X11attr().display,window,cur);
      XUnlockDisplay(cimg::X11attr().display);
      return *this;
    }

    CImgDisplay& set_mouse(const int posx, const int posy) {
      if (is_empty() || is_closed) return *this;
      XLockDisplay(cimg::X11attr().display);
      XWarpPointer(cimg::X11attr().display,None,window,0,0,0,0,posx,posy);
      mouse_x = posx; mouse_y = posy;
      is_moved = false;
      XSync(cimg::X11attr().display, False);
      XUnlockDisplay(cimg::X11attr().display);
      return *this;
    }

    CImgDisplay& set_title(const char *format, ...) {
      if (is_empty()) return *this;
      char tmp[1024] = {0};
      va_list ap;
      va_start(ap, format);
      cimg_std::vsprintf(tmp,format,ap);
      va_end(ap);
      if (title) delete[] title;
      const int s = cimg::strlen(tmp)+1;
      title = new char[s];
      cimg_std::memcpy(title,tmp,s*sizeof(char));
      XLockDisplay(cimg::X11attr().display);
      XStoreName(cimg::X11attr().display,window,tmp);
      XUnlockDisplay(cimg::X11attr().display);
      return *this;
    }

    template<typename T>
    CImgDisplay& display(const CImg<T>& img) {
      if (img.is_empty())
        throw CImgArgumentException("CImgDisplay::display() : Cannot display empty image.");
      if (is_empty()) assign(img.width,img.height);
      return render(img).paint(false);
    }

    CImgDisplay& paint(const bool wait_expose=true) {
      if (is_empty()) return *this;
      XLockDisplay(cimg::X11attr().display);
      _paint(wait_expose);
      XUnlockDisplay(cimg::X11attr().display);
      return *this;
    }

    template<typename T>
    CImgDisplay& render(const CImg<T>& img, const bool flag8=false) {
      if (is_empty()) return *this;
      if (!img)
        throw CImgArgumentException("CImgDisplay::_render_image() : Specified input image (%u,%u,%u,%u,%p) is empty.",
                                    img.width,img.height,img.depth,img.dim,img.data);
      if (img.depth!=1) return render(img.get_projections2d(img.width/2,img.height/2,img.depth/2));
      if (cimg::X11attr().nb_bits==8 && (img.width!=width || img.height!=height)) return render(img.get_resize(width,height,1,-100,1));
      if (cimg::X11attr().nb_bits==8 && !flag8 && img.dim==3) return render(img.get_RGBtoLUT(true),true);

      const T
        *data1 = img.data,
        *data2 = (img.dim>1)?img.ptr(0,0,0,1):data1,
        *data3 = (img.dim>2)?img.ptr(0,0,0,2):data1;

      if (cimg::X11attr().blue_first) cimg::swap(data1,data3);
      XLockDisplay(cimg::X11attr().display);

      if (!normalization || (normalization==3 && cimg::type<T>::string()==cimg::type<unsigned char>::string())) {
        min = max = 0;
        switch (cimg::X11attr().nb_bits) {
        case 8 : { // 256 color palette, no normalization
          _set_colormap(colormap,img.dim);
          unsigned char *const ndata = (img.width==width && img.height==height)?(unsigned char*)data:new unsigned char[img.width*img.height];
          unsigned char *ptrd = (unsigned char*)ndata;
          switch (img.dim) {
          case 1 : for (unsigned int xy = img.width*img.height; xy>0; --xy) (*ptrd++) = (unsigned char)*(data1++);
            break;
          case 2 : for (unsigned int xy = img.width*img.height; xy>0; --xy) {
              const unsigned char R = (unsigned char)*(data1++), G = (unsigned char)*(data2++);
              (*ptrd++) = (R&0xf0) | (G>>4);
            } break;
          default : for (unsigned int xy = img.width*img.height; xy>0; --xy) {
              const unsigned char R = (unsigned char)*(data1++), G = (unsigned char)*(data2++), B = (unsigned char)*(data3++);
              (*ptrd++) = (R&0xe0) | ((G>>5)<<2) | (B>>6);
            }
          }
          if (ndata!=data) { _render_resize(ndata,img.width,img.height,(unsigned char*)data,width,height); delete[] ndata; }
        } break;
        case 16 : { // 16 bits colors, no normalization
          unsigned short *const ndata = (img.width==width && img.height==height)?(unsigned short*)data:new unsigned short[img.width*img.height];
          unsigned char *ptrd = (unsigned char*)ndata;
          const unsigned int M = 248;
          switch (img.dim) {
          case 1 :
            if (cimg::X11attr().byte_order) for (unsigned int xy = img.width*img.height; xy>0; --xy) {
              const unsigned char val = (unsigned char)*(data1++), G = val>>2;
              *(ptrd++) = (val&M) | (G>>3);
              *(ptrd++) = (G<<5) | (G>>1);
            } else for (unsigned int xy = img.width*img.height; xy>0; --xy) {
              const unsigned char val = (unsigned char)*(data1++), G = val>>2;
              *(ptrd++) = (G<<5) | (G>>1);
              *(ptrd++) = (val&M) | (G>>3);
            }
            break;
          case 2 :
            if (cimg::X11attr().byte_order) for (unsigned int xy = img.width*img.height; xy>0; --xy) {
              const unsigned char G = (unsigned char)*(data2++)>>2;
              *(ptrd++) = ((unsigned char)*(data1++)&M) | (G>>3);
              *(ptrd++) = (G<<5);
            } else for (unsigned int xy = img.width*img.height; xy>0; --xy) {
              const unsigned char G = (unsigned char)*(data2++)>>2;
              *(ptrd++) = (G<<5);
              *(ptrd++) = ((unsigned char)*(data1++)&M) | (G>>3);
            }
            break;
          default :
            if (cimg::X11attr().byte_order) for (unsigned int xy = img.width*img.height; xy>0; --xy) {
              const unsigned char G = (unsigned char)*(data2++)>>2;
              *(ptrd++) = ((unsigned char)*(data1++)&M) | (G>>3);
              *(ptrd++) = (G<<5) | ((unsigned char)*(data3++)>>3);
            } else for (unsigned int xy = img.width*img.height; xy>0; --xy) {
              const unsigned char G = (unsigned char)*(data2++)>>2;
              *(ptrd++) = (G<<5) | ((unsigned char)*(data3++)>>3);
              *(ptrd++) = ((unsigned char)*(data1++)&M) | (G>>3);
            }
          }
          if (ndata!=data) { _render_resize(ndata,img.width,img.height,(unsigned short*)data,width,height); delete[] ndata; }
        } break;
        default : { // 24 bits colors, no normalization
          unsigned int *const ndata = (img.width==width && img.height==height)?(unsigned int*)data:new unsigned int[img.width*img.height];
          if (sizeof(int)==4) { // 32 bits int uses optimized version
            unsigned int *ptrd = ndata;
            switch (img.dim) {
            case 1 :
              if (cimg::X11attr().byte_order==cimg::endianness())
                for (unsigned int xy = img.width*img.height; xy>0; --xy) {
                  const unsigned char val = (unsigned char)*(data1++);
                  *(ptrd++) = (val<<16) | (val<<8) | val;
                }
              else
               for (unsigned int xy = img.width*img.height; xy>0; --xy) {
                  const unsigned char val = (unsigned char)*(data1++)<<8;
                  *(ptrd++) = (val<<16) | (val<<8) | val;
                }
              break;
            case 2 :
              if (cimg::X11attr().byte_order==cimg::endianness())
               for (unsigned int xy = img.width*img.height; xy>0; --xy)
                  *(ptrd++) = ((unsigned char)*(data1++)<<16) | ((unsigned char)*(data2++)<<8);
              else
               for (unsigned int xy = img.width*img.height; xy>0; --xy)
                  *(ptrd++) = ((unsigned char)*(data2++)<<16) | ((unsigned char)*(data1++)<<8);
              break;
            default :
              if (cimg::X11attr().byte_order==cimg::endianness())
               for (unsigned int xy = img.width*img.height; xy>0; --xy)
                  *(ptrd++) = ((unsigned char)*(data1++)<<16) | ((unsigned char)*(data2++)<<8) | (unsigned char)*(data3++);
              else
               for (unsigned int xy = img.width*img.height; xy>0; --xy)
                  *(ptrd++) = ((unsigned char)*(data3++)<<24) | ((unsigned char)*(data2++)<<16) | ((unsigned char)*(data1++)<<8);
            }
          } else {
            unsigned char *ptrd = (unsigned char*)ndata;
            switch (img.dim) {
            case 1 :
              if (cimg::X11attr().byte_order) for (unsigned int xy = img.width*img.height; xy>0; --xy) {
                *(ptrd++) = 0;
                *(ptrd++) = (unsigned char)*(data1++);
                *(ptrd++) = 0;
                *(ptrd++) = 0;
              } else for (unsigned int xy = img.width*img.height; xy>0; --xy) {
                *(ptrd++) = 0;
                *(ptrd++) = 0;
                *(ptrd++) = (unsigned char)*(data1++);
                *(ptrd++) = 0;
              }
              break;
            case 2 :
              if (cimg::X11attr().byte_order) cimg::swap(data1,data2);
              for (unsigned int xy = img.width*img.height; xy>0; --xy) {
                *(ptrd++) = 0;
                *(ptrd++) = (unsigned char)*(data2++);
                *(ptrd++) = (unsigned char)*(data1++);
                *(ptrd++) = 0;
              }
              break;
            default :
              if (cimg::X11attr().byte_order) for (unsigned int xy = img.width*img.height; xy>0; --xy) {
                *(ptrd++) = 0;
                *(ptrd++) = (unsigned char)*(data1++);
                *(ptrd++) = (unsigned char)*(data2++);
                *(ptrd++) = (unsigned char)*(data3++);
              } else for (unsigned int xy = img.width*img.height; xy>0; --xy) {
                *(ptrd++) = (unsigned char)*(data3++);
                *(ptrd++) = (unsigned char)*(data2++);
                *(ptrd++) = (unsigned char)*(data1++);
                *(ptrd++) = 0;
              }
            }
          }
          if (ndata!=data) { _render_resize(ndata,img.width,img.height,(unsigned int*)data,width,height); delete[] ndata; }
        }
        };
      } else {
        if (normalization==3) {
          if (cimg::type<T>::is_float()) min = (float)img.minmax(max);
          else { min = (float)cimg::type<T>::min(); max = (float)cimg::type<T>::max(); }
        } else if ((min>max) || normalization==1) min = (float)img.minmax(max);
        const float delta = max-min, mm = delta?delta:1.0f;
        switch (cimg::X11attr().nb_bits) {
        case 8 : { // 256 color palette, with normalization
          _set_colormap(colormap,img.dim);
          unsigned char *const ndata = (img.width==width && img.height==height)?(unsigned char*)data:new unsigned char[img.width*img.height];
          unsigned char *ptrd = (unsigned char*)ndata;
          switch (img.dim) {
          case 1 : for (unsigned int xy = img.width*img.height; xy>0; --xy) {
            const unsigned char R = (unsigned char)(255*(*(data1++)-min)/mm);
            *(ptrd++) = R;
          } break;
          case 2 : for (unsigned int xy = img.width*img.height; xy>0; --xy) {
            const unsigned char
              R = (unsigned char)(255*(*(data1++)-min)/mm),
              G = (unsigned char)(255*(*(data2++)-min)/mm);
            (*ptrd++) = (R&0xf0) | (G>>4);
          } break;
          default :
            for (unsigned int xy = img.width*img.height; xy>0; --xy) {
              const unsigned char
                R = (unsigned char)(255*(*(data1++)-min)/mm),
                G = (unsigned char)(255*(*(data2++)-min)/mm),
                B = (unsigned char)(255*(*(data3++)-min)/mm);
              *(ptrd++) = (R&0xe0) | ((G>>5)<<2) | (B>>6);
            }
          }
          if (ndata!=data) { _render_resize(ndata,img.width,img.height,(unsigned char*)data,width,height); delete[] ndata; }
        } break;
        case 16 : { // 16 bits colors, with normalization
          unsigned short *const ndata = (img.width==width && img.height==height)?(unsigned short*)data:new unsigned short[img.width*img.height];
          unsigned char *ptrd = (unsigned char*)ndata;
          const unsigned int M = 248;
          switch (img.dim) {
          case 1 :
            if (cimg::X11attr().byte_order) for (unsigned int xy = img.width*img.height; xy>0; --xy) {
              const unsigned char val = (unsigned char)(255*(*(data1++)-min)/mm), G = val>>2;
              *(ptrd++) = (val&M) | (G>>3);
              *(ptrd++) = (G<<5) | (val>>3);
            } else for (unsigned int xy = img.width*img.height; xy>0; --xy) {
              const unsigned char val = (unsigned char)(255*(*(data1++)-min)/mm), G = val>>2;
              *(ptrd++) = (G<<5) | (val>>3);
              *(ptrd++) = (val&M) | (G>>3);
            }
            break;
          case 2 :
            if (cimg::X11attr().byte_order) for (unsigned int xy = img.width*img.height; xy>0; --xy) {
              const unsigned char G = (unsigned char)(255*(*(data2++)-min)/mm)>>2;
              *(ptrd++) = ((unsigned char)(255*(*(data1++)-min)/mm)&M) | (G>>3);
              *(ptrd++) = (G<<5);
            } else for (unsigned int xy = img.width*img.height; xy>0; --xy) {
              const unsigned char G = (unsigned char)(255*(*(data2++)-min)/mm)>>2;
              *(ptrd++) = (G<<5);
              *(ptrd++) = ((unsigned char)(255*(*(data1++)-min)/mm)&M) | (G>>3);
            }
            break;
          default :
            if (cimg::X11attr().byte_order) for (unsigned int xy = img.width*img.height; xy>0; --xy) {
              const unsigned char G = (unsigned char)(255*(*(data2++)-min)/mm)>>2;
              *(ptrd++) = ((unsigned char)(255*(*(data1++)-min)/mm)&M) | (G>>3);
              *(ptrd++) = (G<<5) | ((unsigned char)(255*(*(data3++)-min)/mm)>>3);
            } else for (unsigned int xy = img.width*img.height; xy>0; --xy) {
              const unsigned char G = (unsigned char)(255*(*(data2++)-min)/mm)>>2;
              *(ptrd++) = (G<<5) | ((unsigned char)(255*(*(data3++)-min)/mm)>>3);
              *(ptrd++) = ((unsigned char)(255*(*(data1++)-min)/mm)&M) | (G>>3);
            }
          }
          if (ndata!=data) { _render_resize(ndata,img.width,img.height,(unsigned short*)data,width,height); delete[] ndata; }
        } break;
        default : { // 24 bits colors, with normalization
          unsigned int *const ndata = (img.width==width && img.height==height)?(unsigned int*)data:new unsigned int[img.width*img.height];
          if (sizeof(int)==4) { // 32 bits int uses optimized version
            unsigned int *ptrd = ndata;
            switch (img.dim) {
            case 1 :
              if (cimg::X11attr().byte_order==cimg::endianness())
                for (unsigned int xy = img.width*img.height; xy>0; --xy) {
                  const unsigned char val = (unsigned char)(255*(*(data1++)-min)/mm);
                  *(ptrd++) = (val<<16) | (val<<8) | val;
                }
              else
                for (unsigned int xy = img.width*img.height; xy>0; --xy) {
                  const unsigned char val = (unsigned char)(255*(*(data1++)-min)/mm);
                  *(ptrd++) = (val<<24) | (val<<16) | (val<<8);
                }
              break;
            case 2 :
              if (cimg::X11attr().byte_order==cimg::endianness())
                for (unsigned int xy = img.width*img.height; xy>0; --xy)
                  *(ptrd++) =
                    ((unsigned char)(255*(*(data1++)-min)/mm)<<16) |
                    ((unsigned char)(255*(*(data2++)-min)/mm)<<8);
              else
                for (unsigned int xy = img.width*img.height; xy>0; --xy)
                  *(ptrd++) =
                    ((unsigned char)(255*(*(data2++)-min)/mm)<<16) |
                    ((unsigned char)(255*(*(data1++)-min)/mm)<<8);
              break;
            default :
              if (cimg::X11attr().byte_order==cimg::endianness())
                for (unsigned int xy = img.width*img.height; xy>0; --xy)
                  *(ptrd++) =
                    ((unsigned char)(255*(*(data1++)-min)/mm)<<16) |
                    ((unsigned char)(255*(*(data2++)-min)/mm)<<8) |
                    (unsigned char)(255*(*(data3++)-min)/mm);
              else
                for (unsigned int xy = img.width*img.height; xy>0; --xy)
                  *(ptrd++) =
                    ((unsigned char)(255*(*(data3++)-min)/mm)<<24) |
                    ((unsigned char)(255*(*(data2++)-min)/mm)<<16) |
                    ((unsigned char)(255*(*(data1++)-min)/mm)<<8);
            }
          } else {
            unsigned char *ptrd = (unsigned char*)ndata;
            switch (img.dim) {
            case 1 :
              if (cimg::X11attr().byte_order) for (unsigned int xy = img.width*img.height; xy>0; --xy) {
                const unsigned char val = (unsigned char)(255*(*(data1++)-min)/mm);
                (*ptrd++) = 0;
                (*ptrd++) = val;
                (*ptrd++) = val;
                (*ptrd++) = val;
              } else for (unsigned int xy = img.width*img.height; xy>0; --xy) {
                const unsigned char val = (unsigned char)(255*(*(data1++)-min)/mm);
                (*ptrd++) = val;
                (*ptrd++) = val;
                (*ptrd++) = val;
                (*ptrd++) = 0;
              }
              break;
            case 2 :
              if (cimg::X11attr().byte_order) cimg::swap(data1,data2);
              for (unsigned int xy = img.width*img.height; xy>0; --xy) {
                (*ptrd++) = 0;
                (*ptrd++) = (unsigned char)(255*(*(data2++)-min)/mm);
                (*ptrd++) = (unsigned char)(255*(*(data1++)-min)/mm);
                (*ptrd++) = 0;
              }
              break;
            default :
              if (cimg::X11attr().byte_order) for (unsigned int xy = img.width*img.height; xy>0; --xy) {
                (*ptrd++) = 0;
                (*ptrd++) = (unsigned char)(255*(*(data1++)-min)/mm);
                (*ptrd++) = (unsigned char)(255*(*(data2++)-min)/mm);
                (*ptrd++) = (unsigned char)(255*(*(data3++)-min)/mm);
              } else for (unsigned int xy = img.width*img.height; xy>0; --xy) {
                (*ptrd++) = (unsigned char)(255*(*(data3++)-min)/mm);
                (*ptrd++) = (unsigned char)(255*(*(data2++)-min)/mm);
                (*ptrd++) = (unsigned char)(255*(*(data1++)-min)/mm);
                (*ptrd++) = 0;
              }
            }
          }
          if (ndata!=data) { _render_resize(ndata,img.width,img.height,(unsigned int*)data,width,height); delete[] ndata; }
        }
        }
      }
      XUnlockDisplay(cimg::X11attr().display);
      return *this;
    }

    template<typename T>
    const CImgDisplay& snapshot(CImg<T>& img) const {
      if (is_empty()) img.assign();
      else {
        img.assign(width,height,1,3);
        T
          *data1 = img.ptr(0,0,0,0),
          *data2 = img.ptr(0,0,0,1),
          *data3 = img.ptr(0,0,0,2);
        if (cimg::X11attr().blue_first) cimg::swap(data1,data3);
        switch (cimg::X11attr().nb_bits) {
        case 8 : {
          unsigned char *ptrs = (unsigned char*)data;
          for (unsigned int xy = img.width*img.height; xy>0; --xy) {
            const unsigned char val = *(ptrs++);
            *(data1++) = val&0xe0;
            *(data2++) = (val&0x1c)<<3;
            *(data3++) = val<<6;
          }
        } break;
        case 16 : {
          unsigned char *ptrs = (unsigned char*)data;
          if (cimg::X11attr().byte_order) for (unsigned int xy = img.width*img.height; xy>0; --xy) {
            const unsigned char val0 = *(ptrs++), val1 = *(ptrs++);
            *(data1++) = val0&0xf8;
            *(data2++) = (val0<<5) | ((val1&0xe0)>>5);
            *(data3++) = val1<<3;
          } else for (unsigned int xy = img.width*img.height; xy>0; --xy) {
            const unsigned short val0 = *(ptrs++), val1 = *(ptrs++);
            *(data1++) = val1&0xf8;
            *(data2++) = (val1<<5) | ((val0&0xe0)>>5);
            *(data3++) = val0<<3;
          }
        } break;
        default : {
          unsigned char *ptrs = (unsigned char*)data;
          if (cimg::X11attr().byte_order) for (unsigned int xy = img.width*img.height; xy>0; --xy) {
            ++ptrs;
            *(data1++) = *(ptrs++);
            *(data2++) = *(ptrs++);
            *(data3++) = *(ptrs++);
          } else for (unsigned int xy = img.width*img.height; xy>0; --xy) {
            *(data3++) = *(ptrs++);
            *(data2++) = *(ptrs++);
            *(data1++) = *(ptrs++);
            ++ptrs;
          }
        }
        }
      }
      return *this;
    }

    // Windows-based display
    //-----------------------
#elif cimg_display==2
    CLIENTCREATESTRUCT ccs;
    BITMAPINFO bmi;
    unsigned int *data;
    DEVMODE curr_mode;
    HWND window;
    HWND background_window;
    HDC hdc;
    HANDLE thread;
    HANDLE created;
    HANDLE mutex;
    bool mouse_tracking;
    bool visible_cursor;

    static int screen_dimx() {
      DEVMODE mode;
      mode.dmSize = sizeof(DEVMODE);
      mode.dmDriverExtra = 0;
      EnumDisplaySettings(0,ENUM_CURRENT_SETTINGS,&mode);
      return mode.dmPelsWidth;
    }

    static int screen_dimy() {
      DEVMODE mode;
      mode.dmSize = sizeof(DEVMODE);
      mode.dmDriverExtra = 0;
      EnumDisplaySettings(0,ENUM_CURRENT_SETTINGS,&mode);
      return mode.dmPelsHeight;
    }

    static void wait_all() {
      WaitForSingleObject(cimg::Win32attr().wait_event,INFINITE);
    }

    static LRESULT APIENTRY _handle_events(HWND window,UINT msg,WPARAM wParam,LPARAM lParam) {
#ifdef _WIN64
      CImgDisplay* disp = (CImgDisplay*)GetWindowLongPtr(window,GWLP_USERDATA);
#else
      CImgDisplay* disp = (CImgDisplay*)GetWindowLong(window,GWL_USERDATA);
#endif
      MSG st_msg;

      switch (msg) {
      case WM_CLOSE :
        disp->mouse_x = disp->mouse_y = -1;
        disp->window_x = disp->window_y = 0;
        if (disp->button) {
          cimg_std::memmove((void*)(disp->buttons+1),(void*)disp->buttons,512-1);
          disp->button = 0;
        }
        if (disp->key) {
          cimg_std::memmove((void*)(disp->keys+1),(void*)disp->keys,512-1);
          disp->key = 0;
        }
        if (disp->released_key) { cimg_std::memmove((void*)(disp->released_keys+1),(void*)disp->released_keys,512-1); disp->released_key = 0; }
        disp->is_closed = true;
        ReleaseMutex(disp->mutex);
        ShowWindow(disp->window,SW_HIDE);
        disp->is_event = true;
        SetEvent(cimg::Win32attr().wait_event);
        return 0;
      case WM_SIZE : {
        while (PeekMessage(&st_msg,window,WM_SIZE,WM_SIZE,PM_REMOVE)) {}
        WaitForSingleObject(disp->mutex,INFINITE);
        const unsigned int nw = LOWORD(lParam),nh = HIWORD(lParam);
        if (nw && nh && (nw!=disp->width || nh!=disp->height)) {
          disp->window_width = nw;
          disp->window_height = nh;
          disp->mouse_x = disp->mouse_y = -1;
          disp->is_resized = disp->is_event = true;
          SetEvent(cimg::Win32attr().wait_event);
        }
        ReleaseMutex(disp->mutex);
      } break;
      case WM_MOVE : {
        while (PeekMessage(&st_msg,window,WM_SIZE,WM_SIZE,PM_REMOVE)) {}
        WaitForSingleObject(disp->mutex,INFINITE);
        const int nx = (int)(short)(LOWORD(lParam)), ny = (int)(short)(HIWORD(lParam));
        if (nx!=disp->window_x || ny!=disp->window_y) {
          disp->window_x = nx;
          disp->window_y = ny;
          disp->is_moved = disp->is_event = true;
          SetEvent(cimg::Win32attr().wait_event);
        }
        ReleaseMutex(disp->mutex);
      } break;
      case WM_PAINT :
        disp->paint();
        break;
      case WM_KEYDOWN :
        disp->update_iskey((unsigned int)wParam,true);
        if (disp->key) cimg_std::memmove((void*)(disp->keys+1),(void*)disp->keys,512-1);
        disp->key = (unsigned int)wParam;
        if (disp->released_key) { cimg_std::memmove((void*)(disp->released_keys+1),(void*)disp->released_keys,512-1); disp->released_key = 0; }
        disp->is_event = true;
        SetEvent(cimg::Win32attr().wait_event);
        break;
      case WM_MOUSEMOVE : {
        while (PeekMessage(&st_msg,window,WM_MOUSEMOVE,WM_MOUSEMOVE,PM_REMOVE)) {}
        disp->mouse_x = LOWORD(lParam);
        disp->mouse_y = HIWORD(lParam);
#if (_WIN32_WINNT>=0x0400) && !defined(NOTRACKMOUSEEVENT)
        if (!disp->mouse_tracking) {
          TRACKMOUSEEVENT tme;
          tme.cbSize = sizeof(TRACKMOUSEEVENT);
          tme.dwFlags = TME_LEAVE;
          tme.hwndTrack = disp->window;
          if (TrackMouseEvent(&tme)) disp->mouse_tracking = true;
        }
#endif
        if (disp->mouse_x<0 || disp->mouse_y<0 || disp->mouse_x>=disp->dimx() || disp->mouse_y>=disp->dimy())
          disp->mouse_x = disp->mouse_y = -1;
        disp->is_event = true;
        SetEvent(cimg::Win32attr().wait_event);
      } break;
      case WM_MOUSELEAVE : {
        disp->mouse_x = disp->mouse_y = -1;
        disp->mouse_tracking = false;
      } break;
      case WM_LBUTTONDOWN :
        cimg_std::memmove((void*)(disp->buttons+1),(void*)disp->buttons,512-1);
        disp->button|=1U;
        disp->is_event = true;
        SetEvent(cimg::Win32attr().wait_event);
        break;
      case WM_RBUTTONDOWN :
        cimg_std::memmove((void*)(disp->buttons+1),(void*)disp->buttons,512-1);
        disp->button|=2U;
        disp->is_event = true;
        SetEvent(cimg::Win32attr().wait_event);
        break;
      case WM_MBUTTONDOWN :
        cimg_std::memmove((void*)(disp->buttons+1),(void*)disp->buttons,512-1);
        disp->button|=4U;
        disp->is_event = true;
        SetEvent(cimg::Win32attr().wait_event);
        break;
      case 0x020A : // WM_MOUSEWHEEL:
        disp->wheel+=(int)((short)HIWORD(wParam))/120;
        disp->is_event = true;
        SetEvent(cimg::Win32attr().wait_event);
      case WM_KEYUP :
        disp->update_iskey((unsigned int)wParam,false);
        if (disp->key) { cimg_std::memmove((void*)(disp->keys+1),(void*)disp->keys,512-1); disp->key = 0; }
        if (disp->released_key) cimg_std::memmove((void*)(disp->released_keys+1),(void*)disp->released_keys,512-1);
        disp->released_key = (unsigned int)wParam;
        disp->is_event = true;
        SetEvent(cimg::Win32attr().wait_event);
        break;
      case WM_LBUTTONUP :
        cimg_std::memmove((void*)(disp->buttons+1),(void*)disp->buttons,512-1);
        disp->button&=~1U;
        disp->is_event = true;
        SetEvent(cimg::Win32attr().wait_event);
        break;
      case WM_RBUTTONUP :
        cimg_std::memmove((void*)(disp->buttons+1),(void*)disp->buttons,512-1);
        disp->button&=~2U;
        disp->is_event = true;
        SetEvent(cimg::Win32attr().wait_event);
        break;
      case WM_MBUTTONUP :
        cimg_std::memmove((void*)(disp->buttons+1),(void*)disp->buttons,512-1);
        disp->button&=~4U;
        disp->is_event = true;
        SetEvent(cimg::Win32attr().wait_event);
        break;
      case WM_SETCURSOR :
        if (disp->visible_cursor) ShowCursor(TRUE);
        else ShowCursor(FALSE);
        break;
      }
      return DefWindowProc(window,msg,wParam,lParam);
    }

    static DWORD WINAPI _events_thread(void* arg) {
      CImgDisplay *disp = (CImgDisplay*)(((void**)arg)[0]);
      const char *title = (const char*)(((void**)arg)[1]);
      MSG msg;
      delete[] (void**)arg;
      disp->bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
      disp->bmi.bmiHeader.biWidth = disp->width;
      disp->bmi.bmiHeader.biHeight = -(int)disp->height;
      disp->bmi.bmiHeader.biPlanes = 1;
      disp->bmi.bmiHeader.biBitCount = 32;
      disp->bmi.bmiHeader.biCompression = BI_RGB;
      disp->bmi.bmiHeader.biSizeImage = 0;
      disp->bmi.bmiHeader.biXPelsPerMeter = 1;
      disp->bmi.bmiHeader.biYPelsPerMeter = 1;
      disp->bmi.bmiHeader.biClrUsed = 0;
      disp->bmi.bmiHeader.biClrImportant = 0;
      disp->data = new unsigned int[disp->width*disp->height];
      if (!disp->is_fullscreen) { // Normal window
        RECT rect;
        rect.left = rect.top = 0; rect.right = disp->width-1; rect.bottom = disp->height-1;
        AdjustWindowRect(&rect,WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX,false);
        const int border1 = (rect.right-rect.left+1-disp->width)/2, border2 = rect.bottom-rect.top+1-disp->height-border1;
        disp->window = CreateWindowA("MDICLIENT",title?title:" ",
                                     WS_OVERLAPPEDWINDOW | (disp->is_closed?0:WS_VISIBLE), CW_USEDEFAULT,CW_USEDEFAULT,
                                     disp->width + 2*border1, disp->height + border1 + border2,
                                     0,0,0,&(disp->ccs));
        if (!disp->is_closed) {
          GetWindowRect(disp->window,&rect);
          disp->window_x = rect.left + border1;
          disp->window_y = rect.top + border2;
        } else disp->window_x = disp->window_y = 0;
      } else { // Fullscreen window
        const unsigned int sx = screen_dimx(), sy = screen_dimy();
        disp->window = CreateWindowA("MDICLIENT",title?title:" ",
                                     WS_POPUP | (disp->is_closed?0:WS_VISIBLE), (sx-disp->width)/2, (sy-disp->height)/2,
                                     disp->width,disp->height,0,0,0,&(disp->ccs));
        disp->window_x = disp->window_y = 0;
      }
      SetForegroundWindow(disp->window);
      disp->hdc = GetDC(disp->window);
      disp->window_width = disp->width;
      disp->window_height = disp->height;
      disp->flush();
#ifdef _WIN64
      SetWindowLongPtr(disp->window,GWLP_USERDATA,(LONG_PTR)disp);
      SetWindowLongPtr(disp->window,GWLP_WNDPROC,(LONG_PTR)_handle_events);
#else
      SetWindowLong(disp->window,GWL_USERDATA,(LONG)disp);
      SetWindowLong(disp->window,GWL_WNDPROC,(LONG)_handle_events);
#endif
      SetEvent(disp->created);
      while (GetMessage(&msg,0,0,0)) DispatchMessage(&msg);
      return 0;
    }

    CImgDisplay& _update_window_pos() {
      if (!is_closed) {
        RECT rect;
        rect.left = rect.top = 0; rect.right = width-1; rect.bottom = height-1;
        AdjustWindowRect(&rect,WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX,false);
        const int border1 = (rect.right-rect.left+1-width)/2, border2 = rect.bottom-rect.top+1-height-border1;
        GetWindowRect(window,&rect);
        window_x = rect.left + border1;
        window_y = rect.top + border2;
      } else window_x = window_y = -1;
      return *this;
    }

    void _init_fullscreen() {
      background_window = 0;
      if (is_fullscreen && !is_closed) {
        DEVMODE mode;
        unsigned int imode = 0, ibest = 0, bestbpp = 0, bw = ~0U, bh = ~0U;
        for (mode.dmSize = sizeof(DEVMODE), mode.dmDriverExtra = 0; EnumDisplaySettings(0,imode,&mode); ++imode) {
          const unsigned int nw = mode.dmPelsWidth, nh = mode.dmPelsHeight;
          if (nw>=width && nh>=height && mode.dmBitsPerPel>=bestbpp && nw<=bw && nh<=bh) {
            bestbpp = mode.dmBitsPerPel;
            ibest = imode;
            bw = nw; bh = nh;
          }
        }
        if (bestbpp) {
          curr_mode.dmSize = sizeof(DEVMODE); curr_mode.dmDriverExtra = 0;
          EnumDisplaySettings(0,ENUM_CURRENT_SETTINGS,&curr_mode);
          EnumDisplaySettings(0,ibest,&mode);
          ChangeDisplaySettings(&mode,0);
        } else curr_mode.dmSize = 0;

        const unsigned int sx = screen_dimx(), sy = screen_dimy();
        if (sx!=width || sy!=height) {
          CLIENTCREATESTRUCT background_ccs;
          background_window = CreateWindowA("MDICLIENT","",WS_POPUP | WS_VISIBLE, 0,0,sx,sy,0,0,0,&background_ccs);
          SetForegroundWindow(background_window);
        }
      } else curr_mode.dmSize = 0;
    }

    void _desinit_fullscreen() {
      if (is_fullscreen) {
        if (background_window) DestroyWindow(background_window);
        background_window = 0;
        if (curr_mode.dmSize) ChangeDisplaySettings(&curr_mode,0);
        is_fullscreen = false;
      }
    }

    CImgDisplay& _assign(const unsigned int dimw, const unsigned int dimh, const char *ptitle=0,
                         const unsigned int normalization_type=3,
                         const bool fullscreen_flag=false, const bool closed_flag=false) {

      // Allocate space for window title
      const int s = cimg::strlen(ptitle)+1;
      char *tmp_title = s?new char[s]:0;
      if (s) cimg_std::memcpy(tmp_title,ptitle,s*sizeof(char));

      // Destroy previous window if existing
      if (!is_empty()) assign();

      // Set display variables
      width = cimg::min(dimw,(unsigned int)screen_dimx());
      height = cimg::min(dimh,(unsigned int)screen_dimy());
      normalization = normalization_type<4?normalization_type:3;
      is_fullscreen = fullscreen_flag;
      window_x = window_y = 0;
      is_closed = closed_flag;
      visible_cursor = true;
      mouse_tracking = false;
      title = tmp_title;
      flush();
      if (is_fullscreen) _init_fullscreen();

      // Create event thread
      void *arg = (void*)(new void*[2]);
      ((void**)arg)[0]=(void*)this;
      ((void**)arg)[1]=(void*)title;
      unsigned long ThreadID = 0;
      mutex = CreateMutex(0,FALSE,0);
      created = CreateEvent(0,FALSE,FALSE,0);
      thread = CreateThread(0,0,_events_thread,arg,0,&ThreadID);
      WaitForSingleObject(created,INFINITE);
      return *this;
    }

    CImgDisplay& assign() {
      if (is_empty()) return *this;
      DestroyWindow(window);
      TerminateThread(thread,0);
      if (data) delete[] data;
      if (title) delete[] title;
      if (is_fullscreen) _desinit_fullscreen();
      width = height = normalization = window_width = window_height = 0;
      window_x = window_y = 0;
      is_fullscreen = false;
      is_closed = true;
      min = max = 0;
      title = 0;
      flush();
      return *this;
    }

    CImgDisplay& assign(const unsigned int dimw, const unsigned int dimh, const char *title=0,
                        const unsigned int normalization_type=3,
                        const bool fullscreen_flag=false, const bool closed_flag=false) {
      if (!dimw || !dimh) return assign();
      _assign(dimw,dimh,title,normalization_type,fullscreen_flag,closed_flag);
      min = max = 0;
      cimg_std::memset(data,0,sizeof(unsigned int)*width*height);
      return paint();
    }

    template<typename T>
    CImgDisplay& assign(const CImg<T>& img, const char *title=0,
                        const unsigned int normalization_type=3,
                        const bool fullscreen_flag=false, const bool closed_flag=false) {
      if (!img) return assign();
      CImg<T> tmp;
      const CImg<T>& nimg = (img.depth==1)?img:(tmp=img.get_projections2d(img.width/2,img.height/2,img.depth/2));
      _assign(nimg.width,nimg.height,title,normalization_type,fullscreen_flag,closed_flag);
      if (normalization==2) min = (float)nimg.minmax(max);
      return display(nimg);
    }

    template<typename T>
    CImgDisplay& assign(const CImgList<T>& list, const char *title=0,
                        const unsigned int normalization_type=3,
                        const bool fullscreen_flag=false, const bool closed_flag=false) {
      if (!list) return assign();
      CImg<T> tmp;
      const CImg<T> img = list.get_append('x','p'),
        &nimg = (img.depth==1)?img:(tmp=img.get_projections2d(img.width/2,img.height/2,img.depth/2));
      _assign(nimg.width,nimg.height,title,normalization_type,fullscreen_flag,closed_flag);
      if (normalization==2) min = (float)nimg.minmax(max);
      return display(nimg);
    }

    CImgDisplay& assign(const CImgDisplay& win) {
      if (!win) return assign();
      _assign(win.width,win.height,win.title,win.normalization,win.is_fullscreen,win.is_closed);
      cimg_std::memcpy(data,win.data,sizeof(unsigned int)*width*height);
      return paint();
    }

    CImgDisplay& resize(const int nwidth, const int nheight, const bool redraw=true) {
      if (!nwidth || !nheight || (is_empty() && (nwidth<0 || nheight<0))) return assign();
      if (is_empty()) return assign(nwidth,nheight);
      const unsigned int
        tmpdimx=(nwidth>0)?nwidth:(-nwidth*width/100),
        tmpdimy=(nheight>0)?nheight:(-nheight*height/100),
        dimx = tmpdimx?tmpdimx:1,
        dimy = tmpdimy?tmpdimy:1;
      if (window_width!=dimx || window_height!=dimy) {
        RECT rect; rect.left = rect.top = 0; rect.right = dimx-1; rect.bottom = dimy-1;
        AdjustWindowRect(&rect,WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX,false);
        const int cwidth = rect.right-rect.left+1, cheight = rect.bottom-rect.top+1;
        SetWindowPos(window,0,0,0,cwidth,cheight,SWP_NOMOVE | SWP_NOZORDER | SWP_NOCOPYBITS);
      }
      if (width!=dimx || height!=dimy) {
        unsigned int *ndata = new unsigned int[dimx*dimy];
        if (redraw) _render_resize(data,width,height,ndata,dimx,dimy);
        else cimg_std::memset(ndata,0x80,sizeof(unsigned int)*dimx*dimy);
        delete[] data;
        data = ndata;
        bmi.bmiHeader.biWidth = dimx;
        bmi.bmiHeader.biHeight = -(int)dimy;
        width = dimx;
        height = dimy;
      }
      window_width = dimx; window_height = dimy;
      is_resized = false;
      if (is_fullscreen) move((screen_dimx()-width)/2,(screen_dimy()-height)/2);
      if (redraw) return paint();
      return *this;
    }

    CImgDisplay& toggle_fullscreen(const bool redraw=true) {
      if (is_empty()) return *this;
      if (redraw) {
        const unsigned int bufsize = width*height*4;
        void *odata = cimg_std::malloc(bufsize);
        cimg_std::memcpy(odata,data,bufsize);
        assign(width,height,title,normalization,!is_fullscreen,false);
        cimg_std::memcpy(data,odata,bufsize);
        cimg_std::free(odata);
        return paint();
      }
      return assign(width,height,title,normalization,!is_fullscreen,false);
    }

    CImgDisplay& show() {
      if (is_empty()) return *this;
      if (is_closed) {
        is_closed = false;
        if (is_fullscreen) _init_fullscreen();
        ShowWindow(window,SW_SHOW);
        _update_window_pos();
      }
      return paint();
    }

    CImgDisplay& close() {
      if (is_empty()) return *this;
      if (!is_closed && !is_fullscreen) {
        if (is_fullscreen) _desinit_fullscreen();
        ShowWindow(window,SW_HIDE);
        is_closed = true;
        window_x = window_y = 0;
      }
      return *this;
    }

    CImgDisplay& move(const int posx, const int posy) {
      if (is_empty()) return *this;
      if (!is_fullscreen) {
        RECT rect; rect.left = rect.top = 0; rect.right=window_width-1; rect.bottom=window_height-1;
        AdjustWindowRect(&rect,WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX,false);
        const int border1 = (rect.right-rect.left+1-width)/2, border2 = rect.bottom-rect.top+1-height-border1;
        SetWindowPos(window,0,posx-border1,posy-border2,0,0,SWP_NOSIZE | SWP_NOZORDER);
      } else SetWindowPos(window,0,posx,posy,0,0,SWP_NOSIZE | SWP_NOZORDER);
      window_x = posx;
      window_y = posy;
      is_moved = false;
      return show();
    }

    CImgDisplay& show_mouse() {
      if (is_empty()) return *this;
      visible_cursor = true;
      ShowCursor(TRUE);
      SendMessage(window,WM_SETCURSOR,0,0);
      return *this;
    }

    CImgDisplay& hide_mouse() {
      if (is_empty()) return *this;
      visible_cursor = false;
      ShowCursor(FALSE);
      SendMessage(window,WM_SETCURSOR,0,0);
      return *this;
    }

    CImgDisplay& set_mouse(const int posx, const int posy) {
      if (!is_closed && posx>=0 && posy>=0) {
        _update_window_pos();
        const int res = (int)SetCursorPos(window_x+posx,window_y+posy);
        if (res) { mouse_x = posx; mouse_y = posy; }
      }
      return *this;
    }

    CImgDisplay& set_title(const char *format, ...) {
      if (is_empty()) return *this;
      char tmp[1024] = {0};
      va_list ap;
      va_start(ap, format);
      cimg_std::vsprintf(tmp,format,ap);
      va_end(ap);
      if (title) delete[] title;
      const int s = cimg::strlen(tmp)+1;
      title = new char[s];
      cimg_std::memcpy(title,tmp,s*sizeof(char));
      SetWindowTextA(window, tmp);
      return *this;
    }

    template<typename T>
    CImgDisplay& display(const CImg<T>& img) {
      if (img.is_empty())
        throw CImgArgumentException("CImgDisplay::display() : Cannot display empty image.");
      if (is_empty()) assign(img.width,img.height);
      return render(img).paint();
    }

    CImgDisplay& paint() {
      if (!is_closed) {
        WaitForSingleObject(mutex,INFINITE);
        SetDIBitsToDevice(hdc,0,0,width,height,0,0,0,height,data,&bmi,DIB_RGB_COLORS);
        ReleaseMutex(mutex);
      }
      return *this;
    }

    template<typename T>
    CImgDisplay& render(const CImg<T>& img) {
      if (is_empty()) return *this;
      if (!img)
        throw CImgArgumentException("CImgDisplay::_render_image() : Specified input image (%u,%u,%u,%u,%p) is empty.",
                                    img.width,img.height,img.depth,img.dim,img.data);
      if (img.depth!=1) return render(img.get_projections2d(img.width/2,img.height/2,img.depth/2));

      const T
        *data1 = img.data,
        *data2 = (img.dim>=2)?img.ptr(0,0,0,1):data1,
        *data3 = (img.dim>=3)?img.ptr(0,0,0,2):data1;

      WaitForSingleObject(mutex,INFINITE);
      unsigned int
        *const ndata = (img.width==width && img.height==height)?data:new unsigned int[img.width*img.height],
        *ptrd = ndata;

      if (!normalization || (normalization==3 && cimg::type<T>::string()==cimg::type<unsigned char>::string())) {
        min = max = 0;
        switch (img.dim) {
        case 1 : {
          for (unsigned int xy = img.width*img.height; xy>0; --xy) {
            const unsigned char val = (unsigned char)*(data1++);
            *(ptrd++) = (val<<16) | (val<<8) | val;
          }} break;
        case 2 : {
          for (unsigned int xy = img.width*img.height; xy>0; --xy)
            *(ptrd++) = ((unsigned char)*(data1++)<<16) | ((unsigned char)*(data2++)<<8);
        } break;
        default : {
          for (unsigned int xy = img.width*img.height; xy>0; --xy)
            *(ptrd++) = ((unsigned char)*(data1++)<<16) | ((unsigned char)*(data2++)<<8) | (unsigned char)*(data3++);
        }
        }
      } else {
        if (normalization==3) {
          if (cimg::type<T>::is_float()) min = (float)img.minmax(max);
          else { min = (float)cimg::type<T>::min(); max = (float)cimg::type<T>::max(); }
        } else if ((min>max) || normalization==1) min = (float)img.minmax(max);
        const float delta = max-min, mm = delta?delta:1.0f;
        switch (img.dim) {
        case 1 : {
          for (unsigned int xy = img.width*img.height; xy>0; --xy) {
            const unsigned char val = (unsigned char)(255*(*(data1++)-min)/mm);
            *(ptrd++) = (val<<16) | (val<<8) | val;
          }} break;
        case 2 : {
          for (unsigned int xy = img.width*img.height; xy>0; --xy) {
            const unsigned char
              R = (unsigned char)(255*(*(data1++)-min)/mm),
              G = (unsigned char)(255*(*(data2++)-min)/mm);
            *(ptrd++) = (R<<16) | (G<<8);
          }} break;
        default : {
          for (unsigned int xy = img.width*img.height; xy>0; --xy) {
            const unsigned char
              R = (unsigned char)(255*(*(data1++)-min)/mm),
              G = (unsigned char)(255*(*(data2++)-min)/mm),
              B = (unsigned char)(255*(*(data3++)-min)/mm);
            *(ptrd++) = (R<<16) | (G<<8) | B;
          }}
        }
      }
      if (ndata!=data) { _render_resize(ndata,img.width,img.height,data,width,height); delete[] ndata; }
      ReleaseMutex(mutex);
      return *this;
    }

    template<typename T>
    const CImgDisplay& snapshot(CImg<T>& img) const {
      if (is_empty()) img.assign();
      else {
        img.assign(width,height,1,3);
        T
          *data1 = img.ptr(0,0,0,0),
          *data2 = img.ptr(0,0,0,1),
          *data3 = img.ptr(0,0,0,2);
        unsigned int *ptrs = data;
         for (unsigned int xy = img.width*img.height; xy>0; --xy) {
          const unsigned int val = *(ptrs++);
          *(data1++) = (unsigned char)(val>>16);
          *(data2++) = (unsigned char)((val>>8)&0xFF);
          *(data3++) = (unsigned char)(val&0xFF);
        }
      }
      return *this;
    }

    // MacOSX - Carbon-based display
    //-------------------------------
    // (Code by Adrien Reboisson && Romain Blei, supervised by Jean-Marie Favreau)
    //
#elif cimg_display==3
    unsigned int *data;                     // The bits of the picture
    WindowRef carbonWindow;                 // The opaque carbon window struct associated with the display
    MPCriticalRegionID paintCriticalRegion; // Critical section used when drawing
    CGColorSpaceRef csr;                    // Needed for painting
    CGDataProviderRef dataProvider;         // Needed for painting
    CGImageRef imageRef;                    // The image
    UInt32 lastKeyModifiers;                // Buffer storing modifiers state

    // Define the kind of the queries which can be serialized using the event thread.
    typedef enum {
      COM_CREATEWINDOW = 0, // Create window query
      COM_RELEASEWINDOW,    // Release window query
      COM_SHOWWINDOW,       // Show window query
      COM_HIDEWINDOW,       // Hide window query
      COM_SHOWMOUSE,        // Show mouse query
      COM_HIDEMOUSE,        // Hide mouse query
      COM_RESIZEWINDOW,     // Resize window query
      COM_MOVEWINDOW,       // Move window query
      COM_SETTITLE,         // Set window title query
      COM_SETMOUSEPOS       // Set cursor position query
    } CImgCarbonQueryKind;

    // The query destructor send to the event thread.
    struct CbSerializedQuery {
      CImgDisplay* sender;         // Query's sender
      CImgCarbonQueryKind kind;    // The kind of the query sent to the background thread
      short x, y;                  // X:Y values for move/resize operations
      char *c;                     // Char values for window title
      bool createFullScreenWindow; // Boolean value used for full-screen window creation
      bool createClosedWindow;     // Boolean value used for closed-window creation
      bool update;                 // Boolean value used for resize
      bool success;                // Succes or failure of the message, used as return value
      CbSerializedQuery(CImgDisplay *s, CImgCarbonQueryKind k):sender(s),kind(k),success(false) {};

      inline static CbSerializedQuery BuildReleaseWindowQuery(CImgDisplay* sender) {
        return CbSerializedQuery(sender, COM_RELEASEWINDOW);
      }
      inline static CbSerializedQuery BuildCreateWindowQuery(CImgDisplay* sender, const bool fullscreen, const bool closed) {
        CbSerializedQuery q(sender, COM_CREATEWINDOW);
        q.createFullScreenWindow = fullscreen;
        q.createClosedWindow = closed;
        return q;
      }
      inline static CbSerializedQuery BuildShowWindowQuery(CImgDisplay* sender) {
        return CbSerializedQuery(sender, COM_SHOWWINDOW);
      }
      inline static CbSerializedQuery BuildHideWindowQuery(CImgDisplay* sender) {
        return CbSerializedQuery(sender, COM_HIDEWINDOW);
      }
      inline static CbSerializedQuery BuildShowMouseQuery(CImgDisplay* sender) {
        return CbSerializedQuery(sender, COM_SHOWMOUSE);
      }
      inline static CbSerializedQuery BuildHideMouseQuery(CImgDisplay* sender) {
        return CbSerializedQuery(sender, COM_HIDEMOUSE);
      }
      inline static CbSerializedQuery BuildResizeWindowQuery(CImgDisplay* sender, const int x, const int y, bool update) {
        CbSerializedQuery q(sender, COM_RESIZEWINDOW);
        q.x = x, q.y = y;
        q.update = update;
        return q;
      }
      inline static CbSerializedQuery BuildMoveWindowQuery(CImgDisplay* sender, const int x, const int y) {
        CbSerializedQuery q(sender, COM_MOVEWINDOW);
        q.x = x, q.y = y;
        return q;
      }
      inline static CbSerializedQuery BuildSetWindowTitleQuery(CImgDisplay* sender, char* c) {
        CbSerializedQuery q(sender, COM_SETTITLE);
        q.c = c;
        return q;
      }
      inline static CbSerializedQuery BuildSetWindowPosQuery(CImgDisplay* sender, const int x, const int y) {
        CbSerializedQuery q(sender, COM_SETMOUSEPOS);
        q.x = x, q.y = y;
        return q;
      }
    };

    // Send a serialized query in a synchroneous way.
    // @param c Application Carbon global settings.
    // @param m The query to send.
    // @result Success/failure of the operation returned by the event thread.
    bool _CbSendMsg(cimg::CarbonInfo& c, CbSerializedQuery m) {
      MPNotifyQueue(c.com_queue,&m,0,0); // Send the given message
      MPWaitOnSemaphore(c.sync_event,kDurationForever); // Wait end of processing notification
      return m.success;
    }

    // Free the window attached to the current display.
    // @param c Application Carbon global settings.
    // @result Success/failure of the operation.
    bool _CbFreeAttachedWindow(cimg::CarbonInfo& c) {
      if (!_CbSendMsg(c, CbSerializedQuery::BuildReleaseWindowQuery(this))) // Ask the main thread to free the given window
        throw CImgDisplayException("Cannot release window associated with the current display.");
      // If a window existed, ask to release it
      MPEnterCriticalRegion(c.windowListCR,kDurationForever); // Lock the list of the windows
      --c.windowCount; //Decrement the window count
      MPExitCriticalRegion(c.windowListCR); // Unlock the list
      return c.windowCount == 0;
    }

    // Create the window attached to the current display.
    // @param c Application Carbon global settings.
    // @param title The window title, if any.
    // @param fullscreen Shoud we start in fullscreen mode ?
    // @param create_closed If true, the window is created but not displayed.
    // @result Success/failure of the operation.
    void _CbCreateAttachedWindow(cimg::CarbonInfo& c, const char* title, const bool fullscreen, const bool create_closed) {
      if (!_CbSendMsg(c,CbSerializedQuery::BuildCreateWindowQuery(this,fullscreen,create_closed))) // Ask the main thread to create the window
        throw CImgDisplayException("Cannot create the window associated with the current display.");
      if (title) set_title(title); // Set the title, if any
      // Now we can register the window
      MPEnterCriticalRegion(c.windowListCR,kDurationForever); // Lock the list of the windows
      ++c.windowCount; //Increment the window count
      MPExitCriticalRegion(c.windowListCR); // Unlock the list
    }

    // Destroy graphic objects previously allocated. We free the image, the data provider, then the colorspace.
    void _CbFinalizeGraphics() {
      CGImageRelease (imageRef); // Release the picture
      CGDataProviderRelease(dataProvider); // Release the DP
      CGColorSpaceRelease(csr); // Free the cs
    }

    // Create graphic objects associated to a display. We have to create a colormap, a data provider, and the image.
    void _CbInitializeGraphics() {
      csr = CGColorSpaceCreateDeviceRGB(); // Create the color space first
      if (!csr)
        throw CImgDisplayException("CGColorSpaceCreateDeviceRGB() failed.");
      // Create the DP
      dataProvider = CGDataProviderCreateWithData(0,data,height*width*sizeof(unsigned int),0);
      if (!dataProvider)
        throw CImgDisplayException("CGDataProviderCreateWithData() failed.");
      // ... and finally the image.
      if (cimg::endianness())
        imageRef = CGImageCreate(width,height,8,32,width*sizeof(unsigned int),csr,
                                 kCGImageAlphaNoneSkipFirst,dataProvider,0,false,kCGRenderingIntentDefault);
      else
        imageRef = CGImageCreate(width,height,8,32,width*sizeof(unsigned int),csr,
                                 kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Host,dataProvider,0,false,kCGRenderingIntentDefault);
      if (!imageRef)
        throw CImgDisplayException("CGImageCreate() failed.");
    }

    // Reinit graphic objects. Free them, then reallocate all.
    // This is used when image bounds are changed or when data source get invalid.
    void _CbReinitGraphics() {
      MPEnterCriticalRegion(paintCriticalRegion, kDurationForever);
      _CbFinalizeGraphics();
      _CbInitializeGraphics();
      MPExitCriticalRegion(paintCriticalRegion);
    }

    // Convert a point having global coordonates into the window coordonates.
    // We use this function to replace the deprecated GlobalToLocal QuickDraw API.
    // @param mouseEvent The mouse event which triggered the event handler.
    // @param window The window where the event occured.
    // @param point The modified point struct.
    // @result True if the point struct has been converted successfully.
    static bool _CbToLocalPointFromMouseEvent(EventRef mouseEvent, WindowRef window, HIPoint* point) {
      Rect bounds;
      if (GetWindowBounds(window,kWindowStructureRgn,&bounds)==noErr) {
        point->x -= bounds.left;
        point->y -= bounds.top;
        HIViewRef view = NULL;
        if (HIViewGetViewForMouseEvent(HIViewGetRoot(window),mouseEvent,&view)==noErr)
          return HIViewConvertPoint(point, NULL, view) == noErr;
      }
      return false;
    }

    static int screen_dimx() {
      return CGDisplayPixelsWide(kCGDirectMainDisplay);
    }

    static int screen_dimy() {
      return CGDisplayPixelsHigh(kCGDirectMainDisplay);
    }

    CImgDisplay& assign(const unsigned int dimw, const unsigned int dimh, const char *title=0,
                        const unsigned int normalization_type=3,
                        const bool fullscreen_flag=false, const bool closed_flag=false) {
      if (!dimw || !dimh) return assign();
      _assign(dimw,dimh,title,normalization_type,fullscreen_flag,closed_flag);
      min = max = 0;
      cimg_std::memset(data,0,sizeof(unsigned int)*width*height);
      return paint();
    }

    template<typename T>
    CImgDisplay& assign(const CImg<T>& img, const char *title=0,
                        const unsigned int normalization_type=3,
                        const bool fullscreen_flag=false, const bool closed_flag=false) {
      if (!img) return assign();
      CImg<T> tmp;
      const CImg<T>& nimg = (img.depth==1)?img:(tmp=img.get_projections2d(img.width/2,img.height/2,img.depth/2));
      _assign(nimg.width,nimg.height,title,normalization_type,fullscreen_flag,closed_flag);
      if (normalization==2) min = (float)nimg.minmax(max);
      return display(nimg);
    }

    template<typename T>
    CImgDisplay& assign(const CImgList<T>& list, const char *title=0,
                        const unsigned int normalization_type=3,
                        const bool fullscreen_flag=false, const bool closed_flag=false) {
      if (!list) return assign();
      CImg<T> tmp;
      const CImg<T> img = list.get_append('x','p'),
        &nimg = (img.depth==1)?img:(tmp=img.get_projections2d(img.width/2,img.height/2,img.depth/2));
      _assign(nimg.width,nimg.height,title,normalization_type,fullscreen_flag,closed_flag);
      if (normalization==2) min = (float)nimg.minmax(max);
      return display(nimg);
    }

    CImgDisplay& assign(const CImgDisplay &win) {
      if (!win) return assign();
      _assign(win.width,win.height,win.title,win.normalization,win.is_fullscreen,win.is_closed);
      cimg_std::memcpy(data,win.data,sizeof(unsigned int)*width*height);
      return paint();
    }

    template<typename T>
    CImgDisplay& display(const CImg<T>& img) {
      if (is_empty()) assign(img.width,img.height);
      return render(img).paint();
    }

    CImgDisplay& resize(const int nwidth, const int nheight, const bool redraw=true) {
      if (!nwidth || !nheight || (is_empty() && (nwidth<0 || nheight<0))) return assign();
      if (is_empty()) return assign(nwidth,nheight);
      const unsigned int
        tmpdimx = (nwidth>0)?nwidth:(-nwidth*width/100),
        tmpdimy = (nheight>0)?nheight:(-nheight*height/100),
        dimx = tmpdimx?tmpdimx:1,
        dimy = tmpdimy?tmpdimy:1;
      cimg::CarbonInfo& c = cimg::CarbonAttr();

      if ((window_width!=dimx || window_height!=dimy) &&
          !_CbSendMsg(c,CbSerializedQuery::BuildResizeWindowQuery(this,dimx,dimy,redraw)))
        throw CImgDisplayException("CImgDisplay::resize() : Cannot resize the window associated to the current display.");

      if (width!=dimx || height!=dimy) {
        unsigned int *ndata = new unsigned int[dimx*dimy];
        if (redraw) _render_resize(data,width,height,ndata,dimx,dimy);
        else cimg_std::memset(ndata,0x80,sizeof(unsigned int)*dimx*dimy);
        unsigned int const* old_data = data;
        data = ndata;
        delete[] old_data;
        _CbReinitGraphics();
      }
      window_width = width = dimx; window_height = height = dimy;
      is_resized = false;
      if (is_fullscreen) move((screen_dimx()-width)/2,(screen_dimy()-height)/2);
      if (redraw) return paint();
      return *this;
    }

    CImgDisplay& move(const int posx, const int posy) {
      if (is_empty()) return *this;
      if (!is_fullscreen) {
        // If the operation succeeds, window_x and window_y are updated by the event thread
        cimg::CarbonInfo& c = cimg::CarbonAttr();
        // Send the query
        if (!_CbSendMsg(c,CbSerializedQuery::BuildMoveWindowQuery(this,posx,posy)))
          throw CImgDisplayException("CImgDisplay::move() : Cannot move the window associated to the current display.");
      }
      return show();
    }

    CImgDisplay& set_mouse(const int posx, const int posy) {
      if (!is_closed && posx>=0 && posy>=0) {
        // If the operation succeeds, mouse_x and mouse_y are updated by the event thread
        cimg::CarbonInfo& c = cimg::CarbonAttr();
        // Send the query
        if (!_CbSendMsg(c,CbSerializedQuery::BuildSetWindowPosQuery(this,posx,posy)))
          throw CImgDisplayException("CImgDisplay::set_mouse() : Cannot set the mouse position to the current display.");
      }
      return *this;
    }

    CImgDisplay& hide_mouse() {
      if (is_empty()) return *this;
      cimg::CarbonInfo& c = cimg::CarbonAttr();
      // Send the query
      if (!_CbSendMsg(c,CbSerializedQuery::BuildHideMouseQuery(this)))
        throw CImgDisplayException("CImgDisplay::hide_mouse() : Cannot hide the mouse associated to the current display.");
      return *this;
    }

    CImgDisplay& show_mouse() {
      if (is_empty()) return *this;
      cimg::CarbonInfo& c = cimg::CarbonAttr();
      // Send the query
      if (!_CbSendMsg(c,CbSerializedQuery::BuildShowMouseQuery(this)))
        throw CImgDisplayException("CImgDisplay::show_mouse() : Cannot show the mouse associated to the current display.");
      return *this;
    }

    static void wait_all() {
      cimg::CarbonInfo& c = cimg::CarbonAttr();
      MPWaitOnSemaphore(c.wait_event,kDurationForever);
    }

    CImgDisplay& show() {
      if (is_empty()) return *this;
      if (is_closed) {
        cimg::CarbonInfo& c = cimg::CarbonAttr();
        if (!_CbSendMsg(c,CbSerializedQuery::BuildShowWindowQuery(this)))
          throw CImgDisplayException("CImgDisplay::show() : Cannot show the window associated to the current display.");
      }
      return paint();
    }

    CImgDisplay& close() {
      if (is_empty()) return *this;
      if (!is_closed && !is_fullscreen) {
        cimg::CarbonInfo& c = cimg::CarbonAttr();
        // If the operation succeeds, window_x and window_y are updated on the event thread
        if (!_CbSendMsg(c,CbSerializedQuery::BuildHideWindowQuery(this)))
          throw CImgDisplayException("CImgDisplay::close() : Cannot hide the window associated to the current display.");
      }
      return *this;
    }

    CImgDisplay& set_title(const char *format, ...) {
      if (is_empty()) return *this;
      char tmp[1024] = {0};
      va_list ap;
      va_start(ap, format);
      cimg_std::vsprintf(tmp,format,ap);
      va_end(ap);
      if (title) delete[] title;
      const int s = cimg::strlen(tmp)+1;
      title = new char[s];
      cimg_std::memcpy(title,tmp,s*sizeof(char));
      cimg::CarbonInfo& c = cimg::CarbonAttr();
      if (!_CbSendMsg(c,CbSerializedQuery::BuildSetWindowTitleQuery(this,tmp)))
        throw CImgDisplayException("CImgDisplay::set_title() : Cannot set the window title associated to the current display.");
      return *this;
    }

    CImgDisplay& paint() {
      if (!is_closed) {
        MPEnterCriticalRegion(paintCriticalRegion,kDurationForever);
        CGrafPtr portPtr = GetWindowPort(carbonWindow);
        CGContextRef currentContext = 0;
        QDBeginCGContext(portPtr,&currentContext);
        CGContextSetRGBFillColor(currentContext,255,255,255,255);
        CGContextFillRect(currentContext,CGRectMake(0,0,window_width,window_height));
        CGContextDrawImage(currentContext,CGRectMake(0,int(window_height-height)<0?0:window_height-height,width,height),imageRef);
        CGContextFlush(currentContext);
        QDEndCGContext(portPtr, &currentContext);
        MPExitCriticalRegion(paintCriticalRegion);
      }
      return *this;
    }

    template<typename T>
    CImgDisplay& render(const CImg<T>& img) {
      if (is_empty()) return *this;
      if (!img)
        throw CImgArgumentException("CImgDisplay::_render_image() : Specified input image (%u,%u,%u,%u,%p) is empty.",
                                    img.width,img.height,img.depth,img.dim,img.data);
      if (img.depth!=1) return render(img.get_projections2d(img.width/2,img.height/2,img.depth/2));
      const T
        *data1 = img.data,
        *data2 = (img.dim>=2)?img.ptr(0,0,0,1):data1,
        *data3 = (img.dim>=3)?img.ptr(0,0,0,2):data1;
      MPEnterCriticalRegion(paintCriticalRegion, kDurationForever);
      unsigned int
        *const ndata = (img.width==width && img.height==height)?data:new unsigned int[img.width*img.height],
        *ptrd = ndata;
      if (!normalization || (normalization==3 && cimg::type<T>::string()==cimg::type<unsigned char>::string())) {
        min = max = 0;
        for (unsigned int xy = img.width*img.height; xy>0; --xy)
          *(ptrd++) = ((unsigned char)*(data1++)<<16) | ((unsigned char)*(data2++)<<8) | (unsigned char)*(data3++);
      } else {
        if (normalization==3) {
          if (cimg::type<T>::is_float()) min = (float)img.minmax(max);
          else {
            min = (float)cimg::type<T>::min();
            max = (float)cimg::type<T>::max();
          }
        } else if ((min>max) || normalization==1) min = (float)img.minmax(max);
        const float delta = max-min, mm = delta?delta:1.0f;
        for (unsigned int xy = img.width*img.height; xy>0; --xy) {
          const unsigned char
            R = (unsigned char)(255*(*(data1++)-min)/mm),
            G = (unsigned char)(255*(*(data2++)-min)/mm),
            B = (unsigned char)(255*(*(data3++)-min)/mm);
          *(ptrd++) = (R<<16) | (G<<8) | (B);
        }
      }
      if (ndata!=data) {
        _render_resize(ndata,img.width,img.height,data,width,height);
        delete[] ndata;
      }
      MPExitCriticalRegion(paintCriticalRegion);
      return *this;
    }

    template<typename T>
    const CImgDisplay& snapshot(CImg<T>& img) const {
      if (is_empty()) img.assign();
      else {
        img.assign(width,height,1,3);
        T
          *data1 = img.ptr(0,0,0,0),
          *data2 = img.ptr(0,0,0,1),
          *data3 = img.ptr(0,0,0,2);
        unsigned int *ptrs = data;
        for (unsigned int xy = img.width*img.height; xy>0; --xy) {
          const unsigned int val = *(ptrs++);
          *(data1++) = (unsigned char)(val>>16);
          *(data2++) = (unsigned char)((val>>8)&0xFF);
          *(data3++) = (unsigned char)(val&0xFF);
        }
      }
      return *this;
    }

    CImgDisplay& toggle_fullscreen(const bool redraw=true) {
      if (is_empty()) return *this;
      if (redraw) {
        const unsigned int bufsize = width*height*4;
        void *odata = cimg_std::malloc(bufsize);
        cimg_std::memcpy(odata,data,bufsize);
        assign(width,height,title,normalization,!is_fullscreen,false);
        cimg_std::memcpy(data,odata,bufsize);
        cimg_std::free(odata);
        return paint();
      }
      return assign(width,height,title,normalization,!is_fullscreen,false);
    }

    static OSStatus CarbonEventHandler(EventHandlerCallRef myHandler, EventRef theEvent, void* userData) {
      OSStatus result = eventNotHandledErr;
      CImgDisplay* disp = (CImgDisplay*) userData;
      (void)myHandler; // Avoid "unused parameter"
      cimg::CarbonInfo& c = cimg::CarbonAttr();
      // Gets the associated display
      if (disp) {
        // Window events are always handled
        if (GetEventClass(theEvent)==kEventClassWindow) switch (GetEventKind (theEvent)) {
        case kEventWindowClose :
          disp->mouse_x = disp->mouse_y = -1;
          disp->window_x = disp->window_y = 0;
          if (disp->button) {
            cimg_std::memmove((void*)(disp->buttons+1),(void*)disp->buttons,512-1);
            disp->button = 0;
          }
          if (disp->key) {
            cimg_std::memmove((void*)(disp->keys+1),(void*)disp->keys,512-1);
            disp->key = 0;
          }
          if (disp->released_key) { cimg_std::memmove((void*)(disp->released_keys+1),(void*)disp->released_keys,512-1); disp->released_key = 0; }
          disp->is_closed = true;
          HideWindow(disp->carbonWindow);
          disp->is_event = true;
          MPSignalSemaphore(c.wait_event);
          result = noErr;
          break;
          // There is a lot of case where we have to redraw our window
        case kEventWindowBoundsChanging :
        case kEventWindowResizeStarted :
        case kEventWindowCollapsed : //Not sure it's really needed :-)
          break;
        case kEventWindowZoomed :
        case kEventWindowExpanded :
        case kEventWindowResizeCompleted : {
          MPEnterCriticalRegion(disp->paintCriticalRegion, kDurationForever);
          // Now we retrieve the new size of the window
          Rect newContentRect;
          GetWindowBounds(disp->carbonWindow,kWindowContentRgn,&newContentRect);
          const unsigned int
            nw = (unsigned int)(newContentRect.right - newContentRect.left),
            nh = (unsigned int)(newContentRect.bottom - newContentRect.top);

          // Then we update CImg internal settings
          if (nw && nh && (nw!=disp->width || nh!=disp->height)) {
            disp->window_width = nw;
            disp->window_height = nh;
            disp->mouse_x = disp->mouse_y = -1;
            disp->is_resized = true;
          }
          disp->is_event = true;
          MPExitCriticalRegion(disp->paintCriticalRegion);
          disp->paint(); // Coords changed, must update the screen
          MPSignalSemaphore(c.wait_event);
          result = noErr;
        } break;
        case kEventWindowDragStarted :
        case kEventWindowDragCompleted : {
          MPEnterCriticalRegion(disp->paintCriticalRegion, kDurationForever);
          // Now we retrieve the new size of the window
          Rect newContentRect ;
          GetWindowBounds(disp->carbonWindow,kWindowStructureRgn,&newContentRect);
          const int nx = (int)(newContentRect.left), ny = (int)(newContentRect.top);
          // Then we update CImg internal settings
          if (nx!=disp->window_x || ny!=disp->window_y) {
            disp->window_x = nx;
            disp->window_y = ny;
            disp->is_moved = true;
          }
          disp->is_event = true;
          MPExitCriticalRegion(disp->paintCriticalRegion);
          disp->paint(); // Coords changed, must update the screen
          MPSignalSemaphore(c.wait_event);
          result = noErr;
        } break;
          case kEventWindowPaint :
          disp->paint();
          break;
          }

        switch (GetEventClass(theEvent)) {
        case kEventClassKeyboard : {
          if (GetEventKind(theEvent)==kEventRawKeyModifiersChanged) {
            // Apple has special keys named "notifiers", we have to convert this (exotic ?) key handling into the regular CImg processing.
            UInt32 newModifiers;
            if (GetEventParameter(theEvent,kEventParamKeyModifiers,typeUInt32,0,sizeof(UInt32),0,&newModifiers)==noErr) {
              int newKeyCode = -1;
              UInt32 changed = disp->lastKeyModifiers^newModifiers;
              // Find what changed here
              if ((changed & rightShiftKey)!=0) newKeyCode = cimg::keySHIFTRIGHT;
              if ((changed & shiftKey)!=0) newKeyCode = cimg::keySHIFTLEFT;

              // On the Mac, the "option" key = the ALT key
              if ((changed & (optionKey | rightOptionKey))!=0) newKeyCode = cimg::keyALTGR;
              if ((changed & controlKey)!=0) newKeyCode = cimg::keyCTRLLEFT;
              if ((changed & rightControlKey)!=0) newKeyCode = cimg::keyCTRLRIGHT;
              if ((changed & cmdKey)!=0) newKeyCode = cimg::keyAPPLEFT;
              if ((changed & alphaLock)!=0) newKeyCode = cimg::keyCAPSLOCK;
              if (newKeyCode != -1) { // Simulate keystroke
                if (disp->key) cimg_std::memmove((void*)(disp->keys+1),(void*)disp->keys,512-1);
                disp->key = (int)newKeyCode;
              }
              disp->lastKeyModifiers = newModifiers; // Save current state
            }
            disp->is_event = true;
            MPSignalSemaphore(c.wait_event);
          }
          if (GetEventKind(theEvent)==kEventRawKeyDown || GetEventKind(theEvent)==kEventRawKeyRepeat) {
            char keyCode;
            if (GetEventParameter(theEvent,kEventParamKeyMacCharCodes,typeChar,0,sizeof(keyCode),0,&keyCode)==noErr) {
              disp->update_iskey((unsigned int)keyCode,true);
              if (disp->key) cimg_std::memmove((void*)(disp->keys+1),(void*)disp->keys,512-1);
              disp->key = (unsigned int)keyCode;
              if (disp->released_key) { cimg_std::memmove((void*)(disp->released_keys+1),(void*)disp->released_keys,512-1); disp->released_key = 0; }
            }
            disp->is_event = true;
            MPSignalSemaphore(c.wait_event);
          }
        } break;

        case kEventClassMouse :
          switch (GetEventKind(theEvent)) {
          case kEventMouseDragged :
            //  When you push the main button on the Apple mouse while moving it, you got NO kEventMouseMoved msg,
            //  but a kEventMouseDragged one. So we merge them here.
          case kEventMouseMoved :
            HIPoint point;
            if (GetEventParameter(theEvent,kEventParamMouseLocation,typeHIPoint,0,sizeof(point),0,&point)==noErr) {
              if (_CbToLocalPointFromMouseEvent(theEvent,disp->carbonWindow,&point)) {
                disp->mouse_x = (int)point.x;
                disp->mouse_y = (int)point.y;
                if (disp->mouse_x<0 || disp->mouse_y<0 || disp->mouse_x>=disp->dimx() || disp->mouse_y>=disp->dimy())
                  disp->mouse_x = disp->mouse_y = -1;
              } else disp->mouse_x = disp->mouse_y = -1;
            }
            disp->is_event = true;
            MPSignalSemaphore(c.wait_event);
            break;
          case kEventMouseDown :
            UInt16 btn;
            if (GetEventParameter(theEvent,kEventParamMouseButton,typeMouseButton,0,sizeof(btn),0,&btn)==noErr) {
              cimg_std::memmove((void*)(disp->buttons+1),(void*)disp->buttons,512-1);
              if (btn==kEventMouseButtonPrimary) disp->button|=1U;
              // For those who don't have a multi-mouse button (as me), I think it's better to allow the user
              // to emulate a right click by using the Control key
              if ((disp->lastKeyModifiers & (controlKey | rightControlKey))!=0)
                cimg::warn("CImgDisplay::CarbonEventHandler() : Will emulate right click now [Down]");
              if (btn==kEventMouseButtonSecondary || ((disp->lastKeyModifiers & (controlKey | rightControlKey))!=0)) disp->button|=2U;
              if (btn==kEventMouseButtonTertiary) disp->button|=4U;
            }
            disp->is_event = true;
            MPSignalSemaphore(c.wait_event);
            break;
          case kEventMouseWheelMoved :
            EventMouseWheelAxis wheelax;
            SInt32 delta;
            if (GetEventParameter(theEvent,kEventParamMouseWheelAxis,typeMouseWheelAxis,0,sizeof(wheelax),0,&wheelax)==noErr)
              if (wheelax==kEventMouseWheelAxisY) {
                if (GetEventParameter(theEvent,kEventParamMouseWheelDelta,typeLongInteger,0,sizeof(delta),0,&delta)==noErr)
                  if (delta>0) disp->wheel+=delta/120; //FIXME: why 120 ?
                disp->is_event = true;
                MPSignalSemaphore(c.wait_event);
              }
            break;
          }
        }

        switch (GetEventClass(theEvent)) {
        case kEventClassKeyboard :
          if (GetEventKind(theEvent)==kEventRawKeyUp) {
            UInt32 keyCode;
            if (GetEventParameter(theEvent,kEventParamKeyCode,typeUInt32,0,sizeof(keyCode),0,&keyCode)==noErr) {
              disp->update_iskey((unsigned int)keyCode,false);
              if (disp->key) { cimg_std::memmove((void*)(disp->keys+1),(void*)disp->keys,512-1); disp->key = 0; }
              if (disp->released_key) cimg_std::memmove((void*)(disp->released_keys+1),(void*)disp->released_keys,512-1);
              disp->released_key = (int)keyCode;
            }
            disp->is_event = true;
            MPSignalSemaphore(c.wait_event);
          }
          break;

        case kEventClassMouse :
          switch (GetEventKind(theEvent)) {
          case kEventMouseUp :
            UInt16 btn;
            if (GetEventParameter(theEvent,kEventParamMouseButton,typeMouseButton,0,sizeof(btn),0,&btn)==noErr) {
              cimg_std::memmove((void*)(disp->buttons+1),(void*)disp->buttons,512-1);
              if (btn==kEventMouseButtonPrimary) disp->button&=~1U;
              // See note in kEventMouseDown handler.
              if ((disp->lastKeyModifiers & (controlKey | rightControlKey))!=0)
                cimg::warn("CImgDisplay::CarbonEventHandler() : Will emulate right click now [Up]");
              if (btn==kEventMouseButtonSecondary || ((disp->lastKeyModifiers & (controlKey | rightControlKey))!=0)) disp->button&=~2U;
              if (btn==kEventMouseButtonTertiary) disp->button&=~2U;
            }
            disp->is_event = true;
            MPSignalSemaphore(c.wait_event);
            break;
          }
        }
      }
      return (result);
    }

    static void* _events_thread(void* args) {
      (void)args;      // Make the compiler happy
      cimg::CarbonInfo& c = cimg::CarbonAttr();
      pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED,0);
      pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,0);
      MPSignalSemaphore(c.sync_event);  // Notify the caller that all goes fine
      EventRef theEvent;
      EventTargetRef theTarget;
      OSStatus err;
      CbSerializedQuery* query;
      theTarget = GetEventDispatcherTarget();

      // Enter in the main loop
      while (true) {
        pthread_testcancel(); /* Check if cancelation happens */
        err = ReceiveNextEvent(0,0,kDurationImmediate,true,&theEvent); // Fetch new events
        if (err==noErr) { // Received a carbon event, so process it !
          SendEventToEventTarget (theEvent, theTarget);
          ReleaseEvent(theEvent);
        } else if (err == eventLoopTimedOutErr) { // There is no event to process, so check if there is new messages to process
          OSStatus r =MPWaitOnQueue(c.com_queue,(void**)&query,0,0,10*kDurationMillisecond);
          if (r!=noErr) continue; //nothing in the queue or an error.., bye
          // If we're here, we've something to do now.
          if (query) {
            switch (query->kind) {
            case COM_SETMOUSEPOS : { // change the cursor position
              query->success = CGDisplayMoveCursorToPoint(kCGDirectMainDisplay,CGPointMake(query->sender->window_x+query->x,query->sender->window_y+query->y))
                == kCGErrorSuccess;
              if (query->success) {
                query->sender->mouse_x = query->x;
                query->sender->mouse_y = query->y;
              } else cimg::warn("CImgDisplay::_events_thread() : CGDisplayMoveCursorToPoint failed.");
            } break;
            case COM_SETTITLE : { // change the title bar caption
              CFStringRef windowTitle = CFStringCreateWithCString(0,query->c,kCFStringEncodingMacRoman);
              query->success = SetWindowTitleWithCFString(query->sender->carbonWindow,windowTitle)==noErr;
              if (!query->success)
                cimg::warn("CImgDisplay::_events_thread() : SetWindowTitleWithCFString failed.");
              CFRelease(windowTitle);
            } break;
            case COM_RESIZEWINDOW : { // Resize a window
              SizeWindow(query->sender->carbonWindow,query->x,query->y,query->update);
              // If the window has been resized successfully, update display informations
              query->sender->window_width = query->x;
              query->sender->window_height = query->y;
              query->success = true;
            } break;
            case COM_MOVEWINDOW : { // Move a window
              MoveWindow(query->sender->carbonWindow,query->x,query->y,false);
              query->sender->window_x = query->x;
              query->sender->window_y = query->y;
              query->sender->is_moved = false;
              query->success = true;
            } break;
            case COM_SHOWMOUSE : { // Show the mouse
              query->success = CGDisplayShowCursor(kCGDirectMainDisplay)==noErr;
              if (!query->success)
                cimg::warn("CImgDisplay::_events_thread() : CGDisplayShowCursor failed.");
            } break;
            case COM_HIDEMOUSE : { // Hide the mouse
              query->success = CGDisplayHideCursor(kCGDirectMainDisplay)==noErr;
              if (!query->success)
                cimg::warn("CImgDisplay::_events_thread() : CGDisplayHideCursor failed.");
            } break;
            case COM_SHOWWINDOW : { // We've to show a window
              ShowWindow(query->sender->carbonWindow);
              query->success = true;
              query->sender->is_closed = false;
            } break;
            case COM_HIDEWINDOW : { // We've to show a window
              HideWindow(query->sender->carbonWindow);
              query->sender->is_closed = true;
              query->sender->window_x = query->sender->window_y = 0;
              query->success = true;
            } break;
            case COM_RELEASEWINDOW : { // We have to release a given window handle
              query->success = true;
              CFRelease(query->sender->carbonWindow);
            } break;
            case COM_CREATEWINDOW : { // We have to create a window
              query->success = true;
              WindowAttributes  windowAttrs;
              Rect              contentRect;
              if (query->createFullScreenWindow) {
                // To simulate a "true" full screen, we remove menus and close boxes
                windowAttrs = (1L << 9); //Why ? kWindowNoTitleBarAttribute seems to be not defined on 10.3
                // Define a full screen bound rect
                SetRect(&contentRect,0,0,CGDisplayPixelsWide(kCGDirectMainDisplay),CGDisplayPixelsHigh(kCGDirectMainDisplay));
              } else { // Set the window size
                SetRect(&contentRect,0,0,query->sender->width,query->sender->height); // Window will be centered with RepositionWindow.
                // Use default attributes
                windowAttrs = kWindowStandardDocumentAttributes | kWindowStandardHandlerAttribute | kWindowInWindowMenuAttribute | kWindowLiveResizeAttribute;
              }
              // Update window position
              if (query->createClosedWindow) query->sender->window_x = query->sender->window_y = 0;
              else {
                query->sender->window_x = contentRect.left;
                query->sender->window_y = contentRect.top;
              }
              // Update window flags
              query->sender->window_width = query->sender->width;
              query->sender->window_height = query->sender->height;
              query->sender->flush();
              // Create the window
              if (CreateNewWindow(kDocumentWindowClass,windowAttrs,&contentRect,&query->sender->carbonWindow)!=noErr) {
                query->success = false;
                cimg::warn("CImgDisplay::_events_thread() : CreateNewWindow() failed.");
              }
              // Send it to the foreground
              if (RepositionWindow(query->sender->carbonWindow,0,kWindowCenterOnMainScreen)!=noErr) query->success = false;
              // Show it, if needed
              if (!query->createClosedWindow) ShowWindow(query->sender->carbonWindow);

              // Associate a valid event handler
              EventTypeSpec eventList[] = {
                { kEventClassWindow, kEventWindowClose },
                { kEventClassWindow, kEventWindowResizeStarted },
                { kEventClassWindow, kEventWindowResizeCompleted },
                { kEventClassWindow, kEventWindowDragStarted},
                { kEventClassWindow, kEventWindowDragCompleted },
                { kEventClassWindow, kEventWindowPaint },
                { kEventClassWindow, kEventWindowBoundsChanging },
                { kEventClassWindow, kEventWindowCollapsed },
                { kEventClassWindow, kEventWindowExpanded },
                { kEventClassWindow, kEventWindowZoomed },
                { kEventClassKeyboard, kEventRawKeyDown },
                { kEventClassKeyboard, kEventRawKeyUp },
                { kEventClassKeyboard, kEventRawKeyRepeat },
                { kEventClassKeyboard, kEventRawKeyModifiersChanged },
                { kEventClassMouse, kEventMouseMoved },
                { kEventClassMouse, kEventMouseDown },
                { kEventClassMouse, kEventMouseUp },
                { kEventClassMouse, kEventMouseDragged }
              };

              // Set up the handler
              if (InstallWindowEventHandler(query->sender->carbonWindow,NewEventHandlerUPP(CarbonEventHandler),GetEventTypeCount(eventList),
                                            eventList,(void*)query->sender,0)!=noErr) {
                query->success = false;
                cimg::warn("CImgDisplay::_events_thread() : InstallWindowEventHandler failed.");
              }

              // Paint
              query->sender->paint();
            } break;
            default :
              cimg::warn("CImgDisplay::_events_thread() : Received unknow code %d.",query->kind);
            }
            // Signal that the message has been processed
            MPSignalSemaphore(c.sync_event);
          }
        }
      }
      // If we are here, the application is now finished
      pthread_exit(0);
    }

    CImgDisplay& assign() {
      if (is_empty()) return *this;
      cimg::CarbonInfo& c = cimg::CarbonAttr();
      // Destroy the window associated to the display
      _CbFreeAttachedWindow(c);
      // Don't destroy the background thread here.
      // If you check whether _CbFreeAttachedWindow() returned true,
      //   - saying that there were no window left on screen - and
      //   you destroy the background thread here, ReceiveNextEvent won't
      //   work anymore if you create a new window after. So the
      //  background thread must be killed (pthread_cancel() + pthread_join())
      //   only on the application shutdown.

      // Finalize graphics
      _CbFinalizeGraphics();

      // Do some cleanup
      if (data) delete[] data;
      if (title) delete[] title;
      width = height = normalization = window_width = window_height = 0;
      window_x = window_y = 0;
      is_fullscreen = false;
      is_closed = true;
      min = max = 0;
      title = 0;
      flush();
      if (MPDeleteCriticalRegion(paintCriticalRegion)!=noErr)
        throw CImgDisplayException("CImgDisplay()::assign() : MPDeleteCriticalRegion failed.");
      return *this;
    }

    CImgDisplay& _assign(const unsigned int dimw, const unsigned int dimh, const char *ptitle=0,
                         const unsigned int normalization_type=3,
                         const bool fullscreen_flag=false, const bool closed_flag=false) {
      cimg::CarbonInfo& c = cimg::CarbonAttr();

      // Allocate space for window title
      const int s = cimg::strlen(ptitle)+1;
      char *tmp_title = s?new char[s]:0;
      if (s) cimg_std::memcpy(tmp_title,ptitle,s*sizeof(char));

      // Destroy previous window if existing
      if (!is_empty()) assign();

      // Set display variables
      width = cimg::min(dimw,(unsigned int)screen_dimx());
      height = cimg::min(dimh,(unsigned int)screen_dimy());
      normalization = normalization_type<4?normalization_type:3;
      is_fullscreen = fullscreen_flag;
      is_closed = closed_flag;
      lastKeyModifiers = 0;
      title = tmp_title;
      flush();

      // Create the paint CR
      if (MPCreateCriticalRegion(&paintCriticalRegion) != noErr)
        throw CImgDisplayException("CImgDisplay::_assign() : MPCreateCriticalRegion() failed.");

      // Create the thread if it's not already created
      if (c.event_thread==0) {
        // Background thread does not exists, so create it !
        if (pthread_create(&c.event_thread,0,_events_thread,0)!=0)
          throw CImgDisplayException("CImgDisplay::_assign() : pthread_create() failed.");
        // Wait for thread initialization
        MPWaitOnSemaphore(c.sync_event, kDurationForever);
      }

      // Init disp. graphics
      data = new unsigned int[width*height];
      _CbInitializeGraphics();

      // Now ask the thread to create the window
      _CbCreateAttachedWindow(c,ptitle,fullscreen_flag,closed_flag);
      return *this;
    }

#endif

  };

  /*
   #--------------------------------------
   #
   #
   #
   # Definition of the CImg<T> structure
   #
   #
   #
   #--------------------------------------
   */

  //! Class representing an image (up to 4 dimensions wide), each pixel being of type \c T.
  /**
     This is the main class of the %CImg Library. It declares and constructs
     an image, allows access to its pixel values, and is able to perform various image operations.

     \par Image representation

     A %CImg image is defined as an instance of the container \ref CImg<\c T>, which contains a regular grid of pixels,
     each pixel value being of type \c T. The image grid can have up to 4 dimensions : width, height, depth
     and number of channels.
     Usually, the three first dimensions are used to describe spatial coordinates <tt>(x,y,z)</tt>, while the number of channels
     is rather used as a vector-valued dimension (it may describe the R,G,B color channels for instance).
     If you need a fifth dimension, you can use image lists \ref CImgList<\c T> rather than simple images \ref CImg<\c T>.

     Thus, the \ref CImg<\c T> class is able to represent volumetric images of vector-valued pixels,
     as well as images with less dimensions (1D scalar signal, 2D color images, ...).
     Most member functions of the class CImg<\c T> are designed to handle this maximum case of (3+1) dimensions.

     Concerning the pixel value type \c T :
     fully supported template types are the basic C++ types : <tt>unsigned char, char, short, unsigned int, int,
     unsigned long, long, float, double, ... </tt>.
     Typically, fast image display can be done using <tt>CImg<unsigned char></tt> images,
     while complex image processing algorithms may be rather coded using <tt>CImg<float></tt> or <tt>CImg<double></tt>
     images that have floating-point pixel values. The default value for the template T is \c float.
     Using your own template types may be possible. However, you will certainly have to define the complete set
     of arithmetic and logical operators for your class.

     \par Image structure

     The \ref CImg<\c T> structure contains \a six fields :
     - \ref width defines the number of \a columns of the image (size along the X-axis).
     - \ref height defines the number of \a rows of the image (size along the Y-axis).
     - \ref depth defines the number of \a slices of the image (size along the Z-axis).
     - \ref dim defines the number of \a channels of the image (size along the V-axis).
     - \ref data defines a \a pointer to the \a pixel \a data (of type \c T).
     - \ref is_shared is a boolean that tells if the memory buffer \ref data is shared with
       another image.

     You can access these fields publicly although it is recommended to use the dedicated functions
     dimx(), dimy(), dimz(), dimv() and ptr() to do so.
     Image dimensions are not limited to a specific range (as long as you got enough available memory).
     A value of \e 1 usually means that the corresponding dimension is \a flat.
     If one of the dimensions is \e 0, or if the data pointer is null, the image is considered as \e empty.
     Empty images should not contain any pixel data and thus, will not be processed by CImg member functions
     (a CImgInstanceException will be thrown instead).
     Pixel data are stored in memory, in a non interlaced mode (See \ref cimg_storage).

     \par Image declaration and construction

     Declaring an image can be done by using one of the several available constructors.
     Here is a list of the most used :

     - Construct images from arbitrary dimensions :
         - <tt>CImg<char> img;</tt> declares an empty image.
         - <tt>CImg<unsigned char> img(128,128);</tt> declares a 128x128 greyscale image with
         \c unsigned \c char pixel values.
         - <tt>CImg<double> img(3,3);</tt> declares a 3x3 matrix with \c double coefficients.
         - <tt>CImg<unsigned char> img(256,256,1,3);</tt> declares a 256x256x1x3 (color) image
         (colors are stored as an image with three channels).
         - <tt>CImg<double> img(128,128,128);</tt> declares a 128x128x128 volumetric and greyscale image
         (with \c double pixel values).
         - <tt>CImg<> img(128,128,128,3);</tt> declares a 128x128x128 volumetric color image
         (with \c float pixels, which is the default value of the template parameter \c T).
         - \b Note : images pixels are <b>not automatically initialized to 0</b>. You may use the function \ref fill() to
         do it, or use the specific constructor taking 5 parameters like this :
         <tt>CImg<> img(128,128,128,3,0);</tt> declares a 128x128x128 volumetric color image with all pixel values to 0.

     - Construct images from filenames :
         - <tt>CImg<unsigned char> img("image.jpg");</tt> reads a JPEG color image from the file "image.jpg".
         - <tt>CImg<float> img("analyze.hdr");</tt> reads a volumetric image (ANALYZE7.5 format) from the file "analyze.hdr".
         - \b Note : You need to install <a href="http://www.imagemagick.org">ImageMagick</a>
         to be able to read common compressed image formats (JPG,PNG, ...) (See \ref cimg_files_io).

     - Construct images from C-style arrays :
         - <tt>CImg<int> img(data_buffer,256,256);</tt> constructs a 256x256 greyscale image from a \c int* buffer
         \c data_buffer (of size 256x256=65536).
         - <tt>CImg<unsigned char> img(data_buffer,256,256,1,3,false);</tt> constructs a 256x256 color image
         from a \c unsigned \c char* buffer \c data_buffer (where R,G,B channels follow each others).
         - <tt>CImg<unsigned char> img(data_buffer,256,256,1,3,true);</tt> constructs a 256x256 color image
         from a \c unsigned \c char* buffer \c data_buffer (where R,G,B channels are multiplexed).

         The complete list of constructors can be found <a href="#constructors">here</a>.

     \par Most useful functions

     The \ref CImg<\c T> class contains a lot of functions that operates on images.
     Some of the most useful are :

     - operator()(), operator[]() : allows to access or write pixel values.
     - display() : displays the image in a new window.
  **/
  template<typename T>
  struct CImg {

    //! Variable representing the width of the instance image (i.e. dimensions along the X-axis).
    /**
       \remark
       - Prefer using the function CImg<T>::dimx() to get information about the width of an image.
       - Use function CImg<T>::resize() to set a new width for an image. Setting directly the variable \c width would probably
       result in a library crash.
       - Empty images have \c width defined to \c 0.
    **/
    unsigned int width;

    //! Variable representing the height of the instance image (i.e. dimensions along the Y-axis).
    /**
       \remark
       - Prefer using the function CImg<T>::dimy() to get information about the height of an image.
       - Use function CImg<T>::resize() to set a new height for an image. Setting directly the variable \c height would probably
       result in a library crash.
       - 1D signals have \c height defined to \c 1.
       - Empty images have \c height defined to \c 0.
    **/
    unsigned int height;

    //! Variable representing the depth of the instance image (i.e. dimensions along the Z-axis).
    /**
       \remark
       - Prefer using the function CImg<T>::dimz() to get information about the depth of an image.
       - Use function CImg<T>::resize() to set a new depth for an image. Setting directly the variable \c depth would probably
       result in a library crash.
       - Classical 2D images have \c depth defined to \c 1.
       - Empty images have \c depth defined to \c 0.
    **/
    unsigned int depth;

    //! Variable representing the number of channels of the instance image (i.e. dimensions along the V-axis).
    /**
       \remark
       - Prefer using the function CImg<T>::dimv() to get information about the depth of an image.
       - Use function CImg<T>::resize() to set a new vector dimension for an image. Setting directly the variable \c dim would probably
       result in a library crash.
       - Scalar-valued images (one value per pixel) have \c dim defined to \c 1.
       - Empty images have \c depth defined to \c 0.
    **/
    unsigned int dim;

    //! Variable telling if pixel buffer of the instance image is shared with another one.
    bool is_shared;

    //! Pointer to the first pixel of the pixel buffer.
    T *data;

    //! Iterator type for CImg<T>.
    /**
       \remark
       - An \p iterator is a <tt>T*</tt> pointer (address of a pixel value in the pixel buffer).
       - Iterators are not directly used in %CImg functions, they have been introduced for compatibility with the STL.
    **/
    typedef T* iterator;

    //! Const iterator type for CImg<T>.
    /**
       \remark
       - A \p const_iterator is a <tt>const T*</tt> pointer (address of a pixel value in the pixel buffer).
       - Iterators are not directly used in %CImg functions, they have been introduced for compatibility with the STL.
    **/
    typedef const T* const_iterator;

    //! Get value type
    typedef T value_type;

    // Define common T-dependant types.
    typedef typename cimg::superset<T,bool>::type Tbool;
    typedef typename cimg::superset<T,unsigned char>::type Tuchar;
    typedef typename cimg::superset<T,char>::type Tchar;
    typedef typename cimg::superset<T,unsigned short>::type Tushort;
    typedef typename cimg::superset<T,short>::type Tshort;
    typedef typename cimg::superset<T,unsigned int>::type Tuint;
    typedef typename cimg::superset<T,int>::type Tint;
    typedef typename cimg::superset<T,unsigned long>::type Tulong;
    typedef typename cimg::superset<T,long>::type Tlong;
    typedef typename cimg::superset<T,float>::type Tfloat;
    typedef typename cimg::superset<T,double>::type Tdouble;
    typedef typename cimg::last<T,bool>::type boolT;
    typedef typename cimg::last<T,unsigned char>::type ucharT;
    typedef typename cimg::last<T,char>::type charT;
    typedef typename cimg::last<T,unsigned short>::type ushortT;
    typedef typename cimg::last<T,short>::type shortT;
    typedef typename cimg::last<T,unsigned int>::type uintT;
    typedef typename cimg::last<T,int>::type intT;
    typedef typename cimg::last<T,unsigned long>::type ulongT;
    typedef typename cimg::last<T,long>::type longT;
    typedef typename cimg::last<T,float>::type floatT;
    typedef typename cimg::last<T,double>::type doubleT;

    //@}
    //---------------------------
    //
    //! \name Plugins
    //@{
    //---------------------------
#ifdef cimg_plugin
#include cimg_plugin
#endif
#ifdef cimg_plugin1
#include cimg_plugin1
#endif
#ifdef cimg_plugin2
#include cimg_plugin2
#endif
#ifdef cimg_plugin3
#include cimg_plugin3
#endif
#ifdef cimg_plugin4
#include cimg_plugin4
#endif
#ifdef cimg_plugin5
#include cimg_plugin5
#endif
#ifdef cimg_plugin6
#include cimg_plugin6
#endif
#ifdef cimg_plugin7
#include cimg_plugin7
#endif
#ifdef cimg_plugin8
#include cimg_plugin8
#endif
#ifndef cimg_plugin_greycstoration
#define cimg_plugin_greycstoration_count
#endif
#ifndef cimg_plugin_greycstoration_lock
#define cimg_plugin_greycstoration_lock
#endif
#ifndef cimg_plugin_greycstoration_unlock
#define cimg_plugin_greycstoration_unlock
#endif

    //@}

    //--------------------------------------
    //
    //! \name Constructors-Destructor-Copy
    //@{
    //--------------------------------------

    //! Destructor.
    /**
       The destructor destroys the instance image.
       \remark
       - Destructing an empty or shared image does nothing.
       - Otherwise, all memory used to store the pixel data of the instance image is freed.
       - When destroying a non-shared image, be sure that every shared instances of the same image are
       also destroyed to avoid further access to desallocated memory buffers.
    **/
    ~CImg() {
      if (data && !is_shared) delete[] data;
    }

    //! Default constructor.
    /**
       The default constructor creates an empty instance image.
       \remark
       - An empty image does not contain any data and has all of its dimensions \ref width, \ref height, \ref depth, \ref dim
       set to 0 as well as its pointer to the pixel buffer \ref data.
       - An empty image is non-shared.
    **/
    CImg():
      width(0),height(0),depth(0),dim(0),is_shared(false),data(0) {}

    //! Constructs a new image with given size (\p dx,\p dy,\p dz,\p dv).
    /**
       This constructors create an instance image of size (\p dx,\p dy,\p dz,\p dv) with pixels of type \p T.
       \param dx Desired size along the X-axis, i.e. the \ref width of the image.
       \param dy Desired size along the Y-axis, i.e. the \ref height of the image.
       \param dz Desired size along the Z-axis, i.e. the \ref depth of the image.
       \param dv Desired size along the V-axis, i.e. the number of image channels \ref dim.
       \remark
       - If one of the input dimension \p dx,\p dy,\p dz or \p dv is set to 0, the created image is empty
       and all has its dimensions set to 0. No memory for pixel data is then allocated.
       - This constructor creates only non-shared images.
       - Image pixels allocated by this constructor are \b not \b initialized.
       Use the constructor CImg(const unsigned int,const unsigned int,const unsigned int,const unsigned int,const T)
       to get an image of desired size with pixels set to a particular value.
    **/
    explicit CImg(const unsigned int dx, const unsigned int dy=1, const unsigned int dz=1, const unsigned int dv=1):
      is_shared(false) {
      const unsigned long siz = dx*dy*dz*dv;
      if (siz) { width = dx; height = dy; depth = dz; dim = dv; data = new T[siz]; }
      else { width = height = depth = dim = 0; data = 0; }
    }

    //! Construct an image with given size (\p dx,\p dy,\p dz,\p dv) and with pixel having a default value \p val.
    /**
       This constructor creates an instance image of size (\p dx,\p dy,\p dz,\p dv) with pixels of type \p T and sets all pixel
       values of the created instance image to \p val.
       \param dx Desired size along the X-axis, i.e. the \ref width of the image.
       \param dy Desired size along the Y-axis, i.e. the \ref height of the image.
       \param dz Desired size along the Z-axis, i.e. the \ref depth of the image.
       \param dv Desired size along the V-axis, i.e. the number of image channels \p dim.
       \param val Default value for image pixels.
       \remark
       - This constructor has the same properties as CImg(const unsigned int,const unsigned int,const unsigned int,const unsigned int).
    **/
    CImg(const unsigned int dx, const unsigned int dy, const unsigned int dz, const unsigned int dv, const T val):
      is_shared(false) {
      const unsigned long siz = dx*dy*dz*dv;
      if (siz) { width = dx; height = dy; depth = dz; dim = dv; data = new T[siz]; fill(val); }
      else { width = height = depth = dim = 0; data = 0; }
    }

    //! Construct an image with given size (\p dx,\p dy,\p dz,\p dv) and with specified pixel values (int version).
    CImg(const unsigned int dx, const unsigned int dy, const unsigned int dz, const unsigned int dv,
         const int val0, const int val1, ...):width(0),height(0),depth(0),dim(0),is_shared(false),data(0) {
#define _CImg_stdarg(img,a0,a1,N,t) { \
        unsigned int _siz = (unsigned int)N; \
        if (_siz--) { \
          va_list ap; \
          va_start(ap,a1); \
          T *ptrd = (img).data; \
          *(ptrd++) = (T)a0; \
          if (_siz--) { \
            *(ptrd++) = (T)a1; \
            for (; _siz; --_siz) *(ptrd++) = (T)va_arg(ap,t); \
          } \
          va_end(ap); \
        }}
      assign(dx,dy,dz,dv);
      _CImg_stdarg(*this,val0,val1,dx*dy*dz*dv,int);
    }

    //! Construct an image with given size (\p dx,\p dy,\p dz,\p dv) and with specified pixel values (double version).
    CImg(const unsigned int dx, const unsigned int dy, const unsigned int dz, const unsigned int dv,
         const double val0, const double val1, ...):width(0),height(0),depth(0),dim(0),is_shared(false),data(0) {
      assign(dx,dy,dz,dv);
      _CImg_stdarg(*this,val0,val1,dx*dy*dz*dv,double);
    }

    //! Construct an image with given size and with specified values given in a string.
    CImg(const unsigned int dx, const unsigned int dy, const unsigned int dz, const unsigned int dv,
         const char *const values, const bool repeat_pattern):is_shared(false) {
      const unsigned long siz = dx*dy*dz*dv;
      if (siz) { width = dx; height = dy; depth = dz; dim = dv; data = new T[siz]; fill(values,repeat_pattern); }
      else { width = height = depth = dim = 0; data = 0; }
    }

    //! Construct an image from a raw memory buffer.
    /**
       This constructor creates an instance image of size (\p dx,\p dy,\p dz,\p dv) and fill its pixel buffer by
       copying data values from the input raw pixel buffer \p data_buffer.
    **/
    template<typename t>
    CImg(const t *const data_buffer, const unsigned int dx, const unsigned int dy=1,
         const unsigned int dz=1, const unsigned int dv=1, const bool shared=false):is_shared(false) {
      if (shared)
        throw CImgArgumentException("CImg<%s>::CImg() : Cannot construct a shared instance image from a (%s*) buffer "
                                    "(different pixel types).",
                                    pixel_type(),CImg<t>::pixel_type());
      const unsigned long siz = dx*dy*dz*dv;
      if (data_buffer && siz) {
        width = dx; height = dy; depth = dz; dim = dv; data = new T[siz];
        const t *ptrs = data_buffer + siz; cimg_for(*this,ptrd,T) *ptrd = (T)*(--ptrs);
      } else { width = height = depth = dim = 0; data = 0; }
    }

#ifndef cimg_use_visualcpp6
    CImg(const T *const data_buffer, const unsigned int dx, const unsigned int dy=1,
         const unsigned int dz=1, const unsigned int dv=1, const bool shared=false)
#else
    CImg(const T *const data_buffer, const unsigned int dx, const unsigned int dy,
         const unsigned int dz, const unsigned int dv, const bool shared)
#endif
    {
      const unsigned long siz = dx*dy*dz*dv;
      if (data_buffer && siz) {
        width = dx; height = dy; depth = dz; dim = dv; is_shared = shared;
        if (is_shared) data = const_cast<T*>(data_buffer);
        else { data = new T[siz]; cimg_std::memcpy(data,data_buffer,siz*sizeof(T)); }
      } else { width = height = depth = dim = 0; is_shared = false; data = 0; }
    }

   //! Default copy constructor.
    /**
       The default copy constructor creates a new instance image having same dimensions
       (\ref width, \ref height, \ref depth, \ref dim) and same pixel values as the input image \p img.
       \param img The input image to copy.
       \remark
       - If the input image \p img is non-shared or have a different template type \p t != \p T,
       the default copy constructor allocates a new pixel buffer and copy the pixel data
       of \p img into it. In this case, the pointers \ref data to the pixel buffers of the two images are different
       and the resulting instance image is non-shared.
       - If the input image \p img is shared and has the same template type \p t == \p T,
       the default copy constructor does not allocate a new pixel buffer and the resulting instance image
       shares its pixel buffer with the input image \p img, which means that modifying pixels of \p img also modifies
       the created instance image.
       - Copying an image having a different template type \p t != \p T performs a crude static cast conversion of each pixel value from
       type \p t to type \p T.
       - Copying an image having the same template type \p t == \p T is significantly faster.
    **/
    template<typename t>
    CImg(const CImg<t>& img):is_shared(false) {
      const unsigned int siz = img.size();
      if (img.data && siz) {
        width = img.width; height = img.height; depth = img.depth; dim = img.dim; data = new T[siz];
        const t *ptrs = img.data + siz; cimg_for(*this,ptrd,T) *ptrd = (T)*(--ptrs);
      } else { width = height = depth = dim = 0; data = 0; }
    }

    CImg(const CImg<T>& img) {
      const unsigned int siz = img.size();
      if (img.data && siz) {
        width = img.width; height = img.height; depth = img.depth; dim = img.dim; is_shared = img.is_shared;
        if (is_shared) data = const_cast<T*>(img.data);
        else { data = new T[siz]; cimg_std::memcpy(data,img.data,siz*sizeof(T)); }
      } else { width = height = depth = dim = 0; is_shared = false; data = 0; }
    }

   //! Advanced copy constructor.
    /**
       The advanced copy constructor - as the default constructor CImg(const CImg< t >&) - creates a new instance image having same dimensions
       \ref width, \ref height, \ref depth, \ref dim and same pixel values as the input image \p img.
       But it also decides if the created instance image shares its memory with the input image \p img (if the input parameter
       \p shared is set to \p true) or not (if the input parameter \p shared is set to \p false).
       \param img The input image to copy.
       \param shared Boolean flag that decides if the copy is shared on non-shared.
       \remark
       - It is not possible to create a shared copy if the input image \p img is empty or has a different pixel type \p t != \p T.
       - If a non-shared copy of the input image \p img is created, a new memory buffer is allocated for pixel data.
       - If a shared copy of the input image \p img is created, no extra memory is allocated and the pixel buffer of the instance
       image is the same as the one used by the input image \p img.
    **/
    template<typename t>
    CImg(const CImg<t>& img, const bool shared):is_shared(false) {
      if (shared)
        throw CImgArgumentException("CImg<%s>::CImg() : Cannot construct a shared instance image from a CImg<%s> instance "
                                    "(different pixel types).",
                                    pixel_type(),CImg<t>::pixel_type());
      const unsigned int siz = img.size();
      if (img.data && siz) {
        width = img.width; height = img.height; depth = img.depth; dim = img.dim; data = new T[siz];
        const t *ptrs = img.data + siz; cimg_for(*this,ptrd,T) *ptrd = (T)*(--ptrs);
      } else { width = height = depth = dim = 0; data = 0; }
    }

    CImg(const CImg<T>& img, const bool shared) {
      const unsigned int siz = img.size();
      if (img.data && siz) {
        width = img.width; height = img.height; depth = img.depth; dim = img.dim; is_shared = shared;
        if (is_shared) data = const_cast<T*>(img.data);
        else { data = new T[siz]; cimg_std::memcpy(data,img.data,siz*sizeof(T)); }
      } else { width = height = depth = dim = 0; is_shared = false; data = 0; }
    }

    //! Construct an image using dimensions of another image
    template<typename t>
    CImg(const CImg<t>& img, const char *const dimensions):width(0),height(0),depth(0),dim(0),is_shared(false),data(0) {
      assign(img,dimensions);
    }

    //! Construct an image using dimensions of another image, and fill it with a default value
    template<typename t>
    CImg(const CImg<t>& img, const char *const dimensions, const T val):
      width(0),height(0),depth(0),dim(0),is_shared(false),data(0) {
      assign(img,dimensions).fill(val);
    }

   //! Construct an image from an image file.
    /**
       This constructor creates an instance image by reading it from a file.
       \param filename Filename of the image file.
       \remark
       - The image format is deduced from the filename only by looking for the filename extension i.e. without
       analyzing the file itself.
       - Recognized image formats depend on the tools installed on your system or the external libraries you use to link your code with.
       More informations on this topic can be found in cimg_files_io.
       - If the filename is not found, a CImgIOException is thrown by this constructor.
    **/
    explicit CImg(const char *const filename):width(0),height(0),depth(0),dim(0),is_shared(false),data(0) {
      assign(filename);
    }

    //! Construct an image from the content of a CImgDisplay instance.
    explicit CImg(const CImgDisplay &disp):width(0),height(0),depth(0),dim(0),is_shared(false),data(0) {
      disp.snapshot(*this);
    }

    //! In-place version of the default constructor/destructor.
    /**
       This function replaces the instance image by an empty image.
       \remark
       - Memory used by the previous content of the instance image is freed if necessary.
       - If the instance image was initially shared, it is replaced by a (non-shared) empty image.
       - This function is useful to free memory used by an image that is not of use, but which
       has been created in the current code scope (i.e. not destroyed yet).
    **/
    CImg<T>& assign() {
      if (data && !is_shared) delete[] data;
      width = height = depth = dim = 0; is_shared = false; data = 0;
      return *this;
    }

    //! In-place version of the default constructor.
    /**
       This function is strictly equivalent to \ref assign() and has been
       introduced for having a STL-compliant function name.
    **/
    CImg<T>& clear() {
      return assign();
    }

    //! In-place version of the previous constructor.
    /**
       This function replaces the instance image by a new image of size (\p dx,\p dy,\p dz,\p dv) with pixels of type \p T.
       \param dx Desired size along the X-axis, i.e. the \ref width of the image.
       \param dy Desired size along the Y-axis, i.e. the \ref height of the image.
       \param dz Desired size along the Z-axis, i.e. the \ref depth of the image.
       \param dv Desired size along the V-axis, i.e. the number of image channels \p dim.
       - If one of the input dimension \p dx,\p dy,\p dz or \p dv is set to 0, the instance image becomes empty
       and all has its dimensions set to 0. No memory for pixel data is then allocated.
       - Memory buffer used to store previous pixel values is freed if necessary.
       - If the instance image is shared, this constructor actually does nothing more than verifying
       that new and old image dimensions fit.
       - Image pixels allocated by this function are \b not \b initialized.
       Use the function assign(const unsigned int,const unsigned int,const unsigned int,const unsigned int,const T)
       to assign an image of desired size with pixels set to a particular value.
    **/
    CImg<T>& assign(const unsigned int dx, const unsigned int dy=1, const unsigned int dz=1, const unsigned int dv=1) {
      const unsigned long siz = dx*dy*dz*dv;
      if (!siz) return assign();
      const unsigned long curr_siz = size();
      if (siz!=curr_siz) {
        if (is_shared)
          throw CImgArgumentException("CImg<%s>::assign() : Cannot assign image (%u,%u,%u,%u) to shared instance image (%u,%u,%u,%u,%p).",
                                      pixel_type(),dx,dy,dz,dv,width,height,depth,dim,data);
        else { if (data) delete[] data; data = new T[siz]; }
      }
      width = dx; height = dy; depth = dz; dim = dv;
      return *this;
    }

    //! In-place version of the previous constructor.
    /**
       This function replaces the instance image by a new image of size (\p dx,\p dy,\p dz,\p dv) with pixels of type \p T
       and sets all pixel values of the instance image to \p val.
       \param dx Desired size along the X-axis, i.e. the \ref width of the image.
       \param dy Desired size along the Y-axis, i.e. the \ref height of the image.
       \param dz Desired size along the Z-axis, i.e. the \ref depth of the image.
       \param dv Desired size along the V-axis, i.e. the number of image channels \p dim.
       \param val Default value for image pixels.
       \remark
       - This function has the same properties as assign(const unsigned int,const unsigned int,const unsigned int,const unsigned int).
    **/
    CImg<T>& assign(const unsigned int dx, const unsigned int dy, const unsigned int dz, const unsigned int dv, const T val) {
      return assign(dx,dy,dz,dv).fill(val);
    }

    //! In-place version of the previous constructor.
    CImg<T>& assign(const unsigned int dx, const unsigned int dy, const unsigned int dz, const unsigned int dv,
                    const int val0, const int val1, ...) {
      assign(dx,dy,dz,dv);
      _CImg_stdarg(*this,val0,val1,dx*dy*dz*dv,int);
      return *this;
    }

    //! In-place version of the previous constructor.
    CImg<T>& assign(const unsigned int dx, const unsigned int dy, const unsigned int dz, const unsigned int dv,
                    const double val0, const double val1, ...) {
      assign(dx,dy,dz,dv);
      _CImg_stdarg(*this,val0,val1,dx*dy*dz*dv,double);
      return *this;
    }

    //! In-place version of the previous constructor.
    template<typename t>
    CImg<T>& assign(const t *const data_buffer, const unsigned int dx, const unsigned int dy=1,
                    const unsigned int dz=1, const unsigned int dv=1) {
      const unsigned long siz = dx*dy*dz*dv;
      if (!data_buffer || !siz) return assign();
      assign(dx,dy,dz,dv);
      const t *ptrs = data_buffer + siz; cimg_for(*this,ptrd,T) *ptrd = (T)*(--ptrs);
      return *this;
    }

#ifndef cimg_use_visualcpp6
    CImg<T>& assign(const T *const data_buffer, const unsigned int dx, const unsigned int dy=1,
                    const unsigned int dz=1, const unsigned int dv=1)
#else
    CImg<T>& assign(const T *const data_buffer, const unsigned int dx, const unsigned int dy,
                    const unsigned int dz, const unsigned int dv)
#endif
    {
      const unsigned long siz = dx*dy*dz*dv;
      if (!data_buffer || !siz) return assign();
      const unsigned long curr_siz = size();
      if (data_buffer==data && siz==curr_siz) return assign(dx,dy,dz,dv);
      if (is_shared || data_buffer+siz<data || data_buffer>=data+size()) {
        assign(dx,dy,dz,dv);
        if (is_shared) cimg_std::memmove(data,data_buffer,siz*sizeof(T));
        else cimg_std::memcpy(data,data_buffer,siz*sizeof(T));
      } else {
        T *new_data = new T[siz];
        cimg_std::memcpy(new_data,data_buffer,siz*sizeof(T));
        delete[] data; data = new_data; width = dx; height = dy; depth = dz; dim = dv;
      }
      return *this;
    }

    //! In-place version of the previous constructor, allowing to force the shared state of the instance image.
    template<typename t>
    CImg<T>& assign(const t *const data_buffer, const unsigned int dx, const unsigned int dy,
                    const unsigned int dz, const unsigned int dv, const bool shared) {
      if (shared)
        throw CImgArgumentException("CImg<%s>::assign() : Cannot assign buffer (%s*) to shared instance image (%u,%u,%u,%u,%p)"
                                    "(different pixel types).",
                                    pixel_type(),CImg<t>::pixel_type(),width,height,depth,dim,data);
      return assign(data_buffer,dx,dy,dz,dv);
    }

    CImg<T>& assign(const T *const data_buffer, const unsigned int dx, const unsigned int dy,
                    const unsigned int dz, const unsigned int dv, const bool shared) {
      const unsigned long siz = dx*dy*dz*dv;
      if (!data_buffer || !siz) return assign();
      if (!shared) { if (is_shared) assign(); assign(data_buffer,dx,dy,dz,dv); }
      else {
        if (!is_shared) {
          if (data_buffer+siz<data || data_buffer>=data+size()) assign();
          else cimg::warn("CImg<%s>::assign() : Shared instance image has overlapping memory !",
                          pixel_type());
        }
        width = dx; height = dy; depth = dz; dim = dv; is_shared = true;
        data = const_cast<T*>(data_buffer);
      }
      return *this;
    }

    //! In-place version of the default copy constructor.
    /**
       This function assigns a copy of the input image \p img to the current instance image.
       \param img The input image to copy.
       \remark
       - If the instance image is not shared, the content of the input image \p img is copied into a new buffer
       becoming the new pixel buffer of the instance image, while the old pixel buffer is freed if necessary.
       - If the instance image is shared, the content of the input image \p img is copied into the current (shared) pixel buffer
       of the instance image, modifying then the image referenced by the shared instance image. The instance image still remains shared.
    **/
    template<typename t>
    CImg<T>& assign(const CImg<t>& img) {
      return assign(img.data,img.width,img.height,img.depth,img.dim);
    }

    //! In-place version of the advanced constructor.
    /**
       This function - as the simpler function assign(const CImg< t >&) - assigns a copy of the input image \p img to the
       current instance image. But it also decides if the copy is shared (if the input parameter \p shared is set to \c true)
       or non-shared (if the input parameter \p shared is set to \c false).
       \param img The input image to copy.
       \param shared Boolean flag that decides if the copy is shared or non-shared.
       \remark
       - It is not possible to assign a shared copy if the input image \p img is empty or has a different pixel type \p t != \p T.
       - If a non-shared copy of the input image \p img is assigned, a new memory buffer is allocated for pixel data.
       - If a shared copy of the input image \p img is assigned, no extra memory is allocated and the pixel buffer of the instance
       image is the same as the one used by the input image \p img.
    **/
    template<typename t>
    CImg<T>& assign(const CImg<t>& img, const bool shared) {
      return assign(img.data,img.width,img.height,img.depth,img.dim,shared);
    }

    //! In-place version of the previous constructor.
    template<typename t>
    CImg<T>& assign(const CImg<t>& img, const char *const dimensions) {
      if (dimensions) {
        unsigned int siz[4] = { 0,1,1,1 };
        const char *s = dimensions;
        char tmp[256] = { 0 }, c = 0;
        int val = 0;
        for (unsigned int k=0; k<4; ++k) {
          const int err = cimg_std::sscanf(s,"%[-0-9]%c",tmp,&c);
          if (err>=1) {
            const int err = cimg_std::sscanf(s,"%d",&val);
            if (err==1) {
              int val2 = val<0?-val:(c=='%'?val:-1);
              if (val2>=0) {
                val = (int)((k==0?img.width:(k==1?img.height:(k==2?img.depth:img.dim)))*val2/100);
                if (c!='%' && !val) val = 1;
              }
              siz[k] = val;
            }
            s+=cimg::strlen(tmp);
            if (c=='%') ++s;
          }
          if (!err) {
            if (!cimg::strncasecmp(s,"x",1)) { ++s; siz[k] = img.width; }
            else if (!cimg::strncasecmp(s,"y",1)) { ++s; siz[k] = img.height; }
            else if (!cimg::strncasecmp(s,"z",1)) { ++s; siz[k] = img.depth; }
            else if (!cimg::strncasecmp(s,"v",1)) { ++s; siz[k] = img.dim; }
            else if (!cimg::strncasecmp(s,"dx",2)) { s+=2; siz[k] = img.width; }
            else if (!cimg::strncasecmp(s,"dy",2)) { s+=2; siz[k] = img.height; }
            else if (!cimg::strncasecmp(s,"dz",2)) { s+=2; siz[k] = img.depth; }
            else if (!cimg::strncasecmp(s,"dv",2)) { s+=2; siz[k] = img.dim; }
            else if (!cimg::strncasecmp(s,"dimx",4)) { s+=4; siz[k] = img.width; }
            else if (!cimg::strncasecmp(s,"dimy",4)) { s+=4; siz[k] = img.height; }
            else if (!cimg::strncasecmp(s,"dimz",4)) { s+=4; siz[k] = img.depth; }
            else if (!cimg::strncasecmp(s,"dimv",4)) { s+=4; siz[k] = img.dim; }
            else if (!cimg::strncasecmp(s,"width",5)) { s+=5; siz[k] = img.width; }
            else if (!cimg::strncasecmp(s,"height",6)) { s+=6; siz[k] = img.height; }
            else if (!cimg::strncasecmp(s,"depth",5)) { s+=5; siz[k] = img.depth; }
            else if (!cimg::strncasecmp(s,"dim",3)) { s+=3; siz[k] = img.dim; }
            else { ++s; --k; }
          }
        }
        return assign(siz[0],siz[1],siz[2],siz[3]);
      }
      return assign();
    }

    //! In-place version of the previous constructor.
    template<typename t>
    CImg<T>& assign(const CImg<t>& img, const char *const dimensions, const T val) {
      return assign(img,dimensions).fill(val);
    }

    //! In-place version of the previous constructor.
    /**
       This function replaces the instance image by the one that have been read from the given file.
       \param filename Filename of the image file.
       - The image format is deduced from the filename only by looking for the filename extension i.e. without
       analyzing the file itself.
       - Recognized image formats depend on the tools installed on your system or the external libraries you use to link your code with.
       More informations on this topic can be found in cimg_files_io.
       - If the filename is not found, a CImgIOException is thrown by this constructor.
    **/
    CImg<T>& assign(const char *const filename) {
      return load(filename);
    }

    //! In-place version of the previous constructor.
    CImg<T>& assign(const CImgDisplay &disp) {
      disp.snapshot(*this);
      return *this;
    }

    //! Transfer the content of the instance image into another one in a way that memory copies are avoided if possible.
    /**
       The instance image is always empty after a call to this function.
    **/
    template<typename t>
    CImg<t>& transfer_to(CImg<t>& img) {
      img.assign(*this);
      assign();
      return img;
    }

    CImg<T>& transfer_to(CImg<T>& img) {
      if (is_shared || img.is_shared) { img.assign(*this); assign(); } else { img.assign(); swap(img); }
      return img;
    }

    //! Swap all fields of two images. Use with care !
    CImg<T>& swap(CImg<T>& img) {
      cimg::swap(width,img.width);
      cimg::swap(height,img.height);
      cimg::swap(depth,img.depth);
      cimg::swap(dim,img.dim);
      cimg::swap(data,img.data);
      cimg::swap(is_shared,img.is_shared);
      return img;
    }

    //@}
    //-------------------------------------
    //
    //! \name Image Informations
    //@{
    //-------------------------------------

    //! Return the type of the pixel values.
    /**
       \return a string describing the type of the image pixels (template parameter \p T).
       - The string returned may contains spaces (<tt>"unsigned char"</tt>).
       - If the template parameter T does not correspond to a registered type, the string <tt>"unknown"</tt> is returned.
    **/
    static const char* pixel_type() {
      return cimg::type<T>::string();
    }

    //! Return the total number of pixel values in an image.
    /**
       - Equivalent to : dimx() * dimy() * dimz() * dimv().

       \par example:
       \code
       CImg<> img(100,100,1,3);
       if (img.size()==100*100*3) std::fprintf(stderr,"This statement is true");
       \endcode
    **/
    unsigned long size() const {
      return width*height*depth*dim;
    }

    //! Return the number of columns of the instance image (size along the X-axis, i.e image width).
    int dimx() const {
      return (int)width;
    }

    //! Return the number of rows of the instance image (size along the Y-axis, i.e image height).
    int dimy() const {
      return (int)height;
    }

    //! Return the number of slices of the instance image (size along the Z-axis).
    int dimz() const {
      return (int)depth;
    }

    //! Return the number of vector channels of the instance image (size along the V-axis).
    int dimv() const {
      return (int)dim;
    }

    //! Return \c true if image (*this) has the specified width.
    bool is_sameX(const unsigned int dx) const {
      return (width==dx);
    }

    //! Return \c true if images \c (*this) and \c img have same width.
    template<typename t>
    bool is_sameX(const CImg<t>& img) const {
      return is_sameX(img.width);
    }

    //! Return \c true if images \c (*this) and the display \c disp have same width.
    bool is_sameX(const CImgDisplay& disp) const {
      return is_sameX(disp.width);
    }

    //! Return \c true if image (*this) has the specified height.
    bool is_sameY(const unsigned int dy) const {
      return (height==dy);
    }

    //! Return \c true if images \c (*this) and \c img have same height.
    template<typename t>
    bool is_sameY(const CImg<t>& img) const {
      return is_sameY(img.height);
    }

    //! Return \c true if images \c (*this) and the display \c disp have same height.
    bool is_sameY(const CImgDisplay& disp) const {
      return is_sameY(disp.height);
    }

    //! Return \c true if image (*this) has the specified depth.
    bool is_sameZ(const unsigned int dz) const {
      return (depth==dz);
    }

    //! Return \c true if images \c (*this) and \c img have same depth.
    template<typename t>
    bool is_sameZ(const CImg<t>& img) const {
      return is_sameZ(img.depth);
    }

    //! Return \c true if image (*this) has the specified number of channels.
    bool is_sameV(const unsigned int dv) const {
      return (dim==dv);
    }

    //! Return \c true if images \c (*this) and \c img have same dim.
    template<typename t>
    bool is_sameV(const CImg<t>& img) const {
      return is_sameV(img.dim);
    }

    //! Return \c true if image (*this) has the specified width and height.
    bool is_sameXY(const unsigned int dx, const unsigned int dy) const {
      return (is_sameX(dx) && is_sameY(dy));
    }

    //! Return \c true if images have same width and same height.
    template<typename t>
    bool is_sameXY(const CImg<t>& img) const {
      return (is_sameX(img) && is_sameY(img));
    }

    //! Return \c true if image \c (*this) and the display \c disp have same width and same height.
    bool is_sameXY(const CImgDisplay& disp) const {
      return (is_sameX(disp) && is_sameY(disp));
    }

    //! Return \c true if image (*this) has the specified width and depth.
    bool is_sameXZ(const unsigned int dx, const unsigned int dz) const {
      return (is_sameX(dx) && is_sameZ(dz));
    }

    //! Return \c true if images have same width and same depth.
    template<typename t>
    bool is_sameXZ(const CImg<t>& img) const {
      return (is_sameX(img) && is_sameZ(img));
    }

    //! Return \c true if image (*this) has the specified width and number of channels.
    bool is_sameXV(const unsigned int dx, const unsigned int dv) const {
      return (is_sameX(dx) && is_sameV(dv));
    }

    //! Return \c true if images have same width and same number of channels.
    template<typename t>
    bool is_sameXV(const CImg<t>& img) const {
      return (is_sameX(img) && is_sameV(img));
    }

    //! Return \c true if image (*this) has the specified height and depth.
    bool is_sameYZ(const unsigned int dy, const unsigned int dz) const {
      return (is_sameY(dy) && is_sameZ(dz));
    }

    //! Return \c true if images have same height and same depth.
    template<typename t>
    bool is_sameYZ(const CImg<t>& img) const {
      return (is_sameY(img) && is_sameZ(img));
    }

    //! Return \c true if image (*this) has the specified height and number of channels.
    bool is_sameYV(const unsigned int dy, const unsigned int dv) const {
      return (is_sameY(dy) && is_sameV(dv));
    }

    //! Return \c true if images have same height and same number of channels.
    template<typename t>
    bool is_sameYV(const CImg<t>& img) const {
      return (is_sameY(img) && is_sameV(img));
    }

    //! Return \c true if image (*this) has the specified depth and number of channels.
    bool is_sameZV(const unsigned int dz, const unsigned int dv) const {
      return (is_sameZ(dz) && is_sameV(dv));
    }

    //! Return \c true if images have same depth and same number of channels.
    template<typename t>
    bool is_sameZV(const CImg<t>& img) const {
      return (is_sameZ(img) && is_sameV(img));
    }

    //! Return \c true if image (*this) has the specified width, height and depth.
    bool is_sameXYZ(const unsigned int dx, const unsigned int dy, const unsigned int dz) const {
      return (is_sameXY(dx,dy) && is_sameZ(dz));
    }

    //! Return \c true if images have same width, same height and same depth.
    template<typename t>
    bool is_sameXYZ(const CImg<t>& img) const {
      return (is_sameXY(img) && is_sameZ(img));
    }

    //! Return \c true if image (*this) has the specified width, height and depth.
    bool is_sameXYV(const unsigned int dx, const unsigned int dy, const unsigned int dv) const {
      return (is_sameXY(dx,dy) && is_sameV(dv));
    }

    //! Return \c true if images have same width, same height and same number of channels.
    template<typename t>
    bool is_sameXYV(const CImg<t>& img) const {
      return (is_sameXY(img) && is_sameV(img));
    }

    //! Return \c true if image (*this) has the specified width, height and number of channels.
    bool is_sameXZV(const unsigned int dx, const unsigned int dz, const unsigned int dv) const {
      return (is_sameXZ(dx,dz) && is_sameV(dv));
    }

    //! Return \c true if images have same width, same depth and same number of channels.
    template<typename t>
    bool is_sameXZV(const CImg<t>& img) const {
      return (is_sameXZ(img) && is_sameV(img));
    }

    //! Return \c true if image (*this) has the specified height, depth and number of channels.
    bool is_sameYZV(const unsigned int dy, const unsigned int dz, const unsigned int dv) const {
      return (is_sameYZ(dy,dz) && is_sameV(dv));
    }

    //! Return \c true if images have same heigth, same depth and same number of channels.
    template<typename t>
    bool is_sameYZV(const CImg<t>& img) const {
      return (is_sameYZ(img) && is_sameV(img));
    }

    //! Return \c true if image (*this) has the specified width, height, depth and number of channels.
    bool is_sameXYZV(const unsigned int dx, const unsigned int dy, const unsigned int dz, const unsigned int dv) const {
      return (is_sameXYZ(dx,dy,dz) && is_sameV(dv));
    }

    //! Return \c true if images \c (*this) and \c img have same width, same height, same depth and same number of channels.
    template<typename t>
    bool is_sameXYZV(const CImg<t>& img) const {
      return (is_sameXYZ(img) && is_sameV(img));
    }

    //! Return \c true if current image is empty.
    bool is_empty() const {
      return !(data && width && height && depth && dim);
    }

    //! Return \p true if image is not empty.
    operator bool() const {
      return !is_empty();
    }

    //! Return an iterator to the first image pixel
    iterator begin() {
      return data;
    }

    const_iterator begin() const {
      return data;
    }

    //! Return reference to the first image pixel
    const T& first() const {
      return *data;
    }

    T& first() {
       return *data;
    }

    //! Return an iterator pointing after the last image pixel
    iterator end() {
      return data + size();
    }

    const_iterator end() const {
      return data + size();
    }

    //! Return a reference to the last image pixel
    const T& last() const {
       return data[size() - 1];
    }

    T& last() {
       return data[size() - 1];
    }

    //! Return a pointer to the pixel buffer.
    T* ptr() {
      return data;
    }

    const T* ptr() const {
      return data;
    }

    //! Return a pointer to the pixel value located at (\p x,\p y,\p z,\p v).
    /**
       \param x X-coordinate of the pixel.
       \param y Y-coordinate of the pixel.
       \param z Z-coordinate of the pixel.
       \param v V-coordinate of the pixel.

       - When called without parameters, ptr() returns a pointer to the begining of the pixel buffer.
       - If the macro \c 'cimg_debug'>=3, boundary checking is performed and warning messages may appear if
       given coordinates are outside the image range (but function performances decrease).

       \par example:
       \code
       CImg<float> img(100,100,1,1,0);   // Define a 100x100 greyscale image with float-valued pixels.
       float *ptr = ptr(10,10);          // Get a pointer to the pixel located at (10,10).
       float val = *ptr;                 // Get the pixel value.
       \endcode
    **/
#if cimg_debug>=3
    T* ptr(const unsigned int x, const unsigned int y=0, const unsigned int z=0, const unsigned int v=0) {
      const long off = offset(x,y,z,v);
      if (off<0 || off>=(long)size()) {
        cimg::warn("CImg<%s>::ptr() : Asked for a pointer at coordinates (%u,%u,%u,%u) (offset=%ld), "
                   "outside image range (%u,%u,%u,%u) (size=%lu)",
                   pixel_type(),x,y,z,v,off,width,height,depth,dim,size());
        return data;
      }
      return data + off;
    }

    const T* ptr(const unsigned int x, const unsigned int y=0, const unsigned int z=0, const unsigned int v=0) const {
      return const_cast<CImg<T>*>(this)->ptr(x,y,z,v);
    }
#else
    T* ptr(const unsigned int x, const unsigned int y=0, const unsigned int z=0, const unsigned int v=0) {
      return data + (long)x + (long)y*width + (long)z*width*height + (long)v*width*height*depth;
    }

    const T* ptr(const unsigned int x, const unsigned int y=0, const unsigned int z=0, const unsigned int v=0) const {
      return data + (long)x + (long)y*width + (long)z*width*height + (long)v*width*height*depth;
    }
#endif

    //! Return \c true if the memory buffers of the two images overlaps.
    /**
       May happen when using shared images.
    **/
    template<typename t>
    bool is_overlapped(const CImg<t>& img) const {
      const unsigned long csiz = size(), isiz = img.size();
      return !((void*)(data+csiz)<=(void*)img.data || (void*)data>=(void*)(img.data+isiz));
    }

    //! Return the offset of the pixel coordinates (\p x,\p y,\p z,\p v) with respect to the data pointer \c data.
    /**
       \param x X-coordinate of the pixel.
       \param y Y-coordinate of the pixel.
       \param z Z-coordinate of the pixel.
       \param v V-coordinate of the pixel.

       - No checking is done on the validity of the given coordinates.

       \par Example:
       \code
       CImg<float> img(100,100,1,3,0);         // Define a 100x100 color image with float-valued black pixels.
       long off = img.offset(10,10,0,2);       // Get the offset of the blue value of the pixel located at (10,10).
       float val = img[off];                   // Get the blue value of the pixel.
       \endcode
    **/
    long offset(const int x, const int y=0, const int z=0, const int v=0) const {
      return (long)x + (long)y*width + (long)z*width*height + (long)v*width*height*depth;
    }

    //! Fast access to pixel value for reading or writing.
    /**
       \param x X-coordinate of the pixel.
       \param y Y-coordinate of the pixel.
       \param z Z-coordinate of the pixel.
       \param v V-coordinate of the pixel.

       - If one image dimension is equal to 1, it can be omitted in the coordinate list (see example below).
       - If the macro \c 'cimg_debug'>=3, boundary checking is performed and warning messages may appear
       (but function performances decrease).

       \par example:
       \code
       CImg<float> img(100,100,1,3,0);                       // Define a 100x100 color image with float-valued black pixels.
       const float valR = img(10,10,0,0);                    // Read the red component at coordinates (10,10).
       const float valG = img(10,10,0,1);                    // Read the green component at coordinates (10,10)
       const float valB = img(10,10,2);                      // Read the blue component at coordinates (10,10) (Z-coordinate omitted here).
       const float avg = (valR + valG + valB)/3;             // Compute average pixel value.
       img(10,10,0) = img(10,10,1) = img(10,10,2) = avg;     // Replace the pixel (10,10) by the average grey value.
       \endcode
    **/
#if cimg_debug>=3
    T& operator()(const unsigned int x, const unsigned int y=0, const unsigned int z=0, const unsigned int v=0) {
      const long off = offset(x,y,z,v);
      if (!data || off>=(long)size()) {
        cimg::warn("CImg<%s>::operator() : Pixel access requested at (%u,%u,%u,%u) (offset=%ld) "
                   "outside the image range (%u,%u,%u,%u) (size=%lu)",
                   pixel_type(),x,y,z,v,off,width,height,depth,dim,size());
        return *data;
      }
      else return data[off];
    }

    const T& operator()(const unsigned int x, const unsigned int y=0, const unsigned int z=0, const unsigned int v=0) const {
      return const_cast<CImg<T>*>(this)->operator()(x,y,z,v);
    }
#else
    T& operator()(const unsigned int x, const unsigned int y=0, const unsigned int z=0, const unsigned int v=0) {
      return data[(long)x + (long)y*width + (long)z*width*height + (long)v*width*height*depth];
    }

    const T& operator()(const unsigned int x, const unsigned int y=0, const unsigned int z=0, const unsigned int v=0) const {
      return data[(long)x + (long)y*width + (long)z*width*height + (long)v*width*height*depth];
    }
#endif

    //! Fast access to pixel value for reading or writing, using an offset to the image pixel.
    /**
       \param off Offset of the pixel according to the begining of the pixel buffer, given by ptr().

       - If the macro \c 'cimg_debug'>=3, boundary checking is performed and warning messages may appear
       (but function performances decrease).
       - As pixel values are aligned in memory, this operator can sometime useful to access values easier than
       with operator()() (see example below).

       \par example:
       \code
       CImg<float> vec(1,10);        // Define a vector of float values (10 lines, 1 row).
       const float val1 = vec(0,4);  // Get the fifth element using operator()().
       const float val2 = vec[4];    // Get the fifth element using operator[]. Here, val2==val1.
       \endcode
    **/
#if cimg_debug>=3
    T& operator[](const unsigned long off) {
      if (!data || off>=size()) {
        cimg::warn("CImg<%s>::operator[] : Pixel access requested at offset=%lu "
                   "outside the image range (%u,%u,%u,%u) (size=%lu)",
                   pixel_type(),off,width,height,depth,dim,size());
        return *data;
      }
      else return data[off];
    }

    const T& operator[](const unsigned long off) const {
      return const_cast<CImg<T>*>(this)->operator[](off);
    }
#else
    T& operator[](const unsigned long off) {
      return data[off];
    }

    const T& operator[](const unsigned long off) const {
      return data[off];
    }
#endif

    //! Return a reference to the last image value
    T& back() {
      return operator()(size()-1);
    }

    const T& back() const {
      return operator()(size()-1);
    }

    //! Return a reference to the first image value
    T& front() {
      return *data;
    }

    const T& front() const {
      return *data;
    }

    //! Return \c true if pixel (x,y,z,v) is inside image boundaries.
    bool containsXYZV(const int x, const int y=0, const int z=0, const int v=0) const {
      return !is_empty() && x>=0 && x<dimx() && y>=0 && y<dimy() && z>=0 && z<dimz() && v>=0 && v<dimv();
    }

    //! Return \c true if specified referenced value is inside image boundaries. If true, returns pixel coordinates in (x,y,z,v).
    template<typename t>
    bool contains(const T& pixel, t& x, t& y, t& z, t& v) const {
      const unsigned long wh = width*height, whz = wh*depth, siz = whz*dim;
      const T *const ppixel = &pixel;
      if (is_empty() || ppixel<data || ppixel>=data+siz) return false;
      unsigned long off = (unsigned long)(ppixel - data);
      const unsigned long nv = off/whz;
      off%=whz;
      const unsigned long nz = off/wh;
      off%=wh;
      const unsigned long ny = off/width, nx = off%width;
      x = (t)nx; y = (t)ny; z = (t)nz; v = (t)nv;
      return true;
    }

    //! Return \c true if specified referenced value is inside image boundaries. If true, returns pixel coordinates in (x,y,z).
    template<typename t>
    bool contains(const T& pixel, t& x, t& y, t& z) const {
      const unsigned long wh = width*height, whz = wh*depth, siz = whz*dim;
      const T *const ppixel = &pixel;
      if (is_empty() || ppixel<data || ppixel>=data+siz) return false;
      unsigned long off = ((unsigned long)(ppixel - data))%whz;
      const unsigned long nz = off/wh;
      off%=wh;
      const unsigned long ny = off/width, nx = off%width;
      x = (t)nx; y = (t)ny; z = (t)nz;
      return true;
    }

    //! Return \c true if specified referenced value is inside image boundaries. If true, returns pixel coordinates in (x,y).
    template<typename t>
    bool contains(const T& pixel, t& x, t& y) const {
      const unsigned long wh = width*height, siz = wh*depth*dim;
      const T *const ppixel = &pixel;
      if (is_empty() || ppixel<data || ppixel>=data+siz) return false;
      unsigned long off = ((unsigned long)(ppixel - data))%wh;
      const unsigned long ny = off/width, nx = off%width;
      x = (t)nx; y = (t)ny;
      return true;
    }

    //! Return \c true if specified referenced value is inside image boundaries. If true, returns pixel coordinates in (x).
    template<typename t>
    bool contains(const T& pixel, t& x) const {
      const T *const ppixel = &pixel;
      if (is_empty() || ppixel<data || ppixel>=data+size()) return false;
      x = (t)(((unsigned long)(ppixel - data))%width);
      return true;
    }

    //! Return \c true if specified referenced value is inside the image boundaries.
    bool contains(const T& pixel) const {
      const T *const ppixel = &pixel;
      return !is_empty() && ppixel>=data && ppixel<data+size();
    }

    //! Read a pixel value with Dirichlet boundary conditions.
    T& at(const int off, const T out_val) {
      return (off<0 || off>=(int)size())?(cimg::temporary(out_val)=out_val):(*this)[off];
    }

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

    //! Read a pixel value with Neumann boundary conditions.
    T& at(const int off) {
      if (!size())
        throw CImgInstanceException("CImg<%s>::at() : Instance image is empty.",
                                    pixel_type());
      return _at(off);
    }

    T at(const int off) const {
      if (!size())
        throw CImgInstanceException("CImg<%s>::at() : Instance image is empty.",
                                    pixel_type());
      return _at(off);
    }

    T& _at(const int off) {
      const unsigned int siz = (unsigned int)size();
      return (*this)[off<0?0:(unsigned int)off>=siz?siz-1:off];
    }

    T _at(const int off) const {
      const unsigned int siz = (unsigned int)size();
      return (*this)[off<0?0:(unsigned int)off>=siz?siz-1:off];
    }

    //! Read a pixel value with Dirichlet boundary conditions.
    T& atXYZV(const int x, const int y, const int z, const int v, const T out_val) {
      return (x<0 || y<0 || z<0 || v<0 || x>=dimx() || y>=dimy() || z>=dimz() || v>=dimv())?
        (cimg::temporary(out_val)=out_val):(*this)(x,y,z,v);
    }

    T atXYZV(const int x, const int y, const int z, const int v, const T out_val) const {
      return (x<0 || y<0 || z<0 || v<0 || x>=dimx() || y>=dimy() || z>=dimz() || v>=dimv())?out_val:(*this)(x,y,z,v);
    }

    //! Read a pixel value with Neumann boundary conditions.
    T& atXYZV(const int x, const int y, const int z, const int v) {
      if (is_empty())
        throw CImgInstanceException("CImg<%s>::atXYZV() : Instance image is empty.",
                                    pixel_type());
      return _atXYZV(x,y,z,v);
    }

    T atXYZV(const int x, const int y, const int z, const int v) const {
      if (is_empty())
        throw CImgInstanceException("CImg<%s>::atXYZV() : Instance image is empty.",
                                    pixel_type());
      return _atXYZV(x,y,z,v);
    }

    T& _atXYZV(const int x, const int y, const int z, const int v) {
      return (*this)(x<0?0:(x>=dimx()?dimx()-1:x), y<0?0:(y>=dimy()?dimy()-1:y),
                     z<0?0:(z>=dimz()?dimz()-1:z), v<0?0:(v>=dimv()?dimv()-1:v));
    }

    T _atXYZV(const int x, const int y, const int z, const int v) const {
      return (*this)(x<0?0:(x>=dimx()?dimx()-1:x), y<0?0:(y>=dimy()?dimy()-1:y),
                     z<0?0:(z>=dimz()?dimz()-1:z), v<0?0:(v>=dimv()?dimv()-1:v));
    }

    //! Read a pixel value with Dirichlet boundary conditions for the three first coordinates (\c x,\c y,\c z).
    T& atXYZ(const int x, const int y, const int z, const int v, const T out_val) {
      return (x<0 || y<0 || z<0 || x>=dimx() || y>=dimy() || z>=dimz())?
        (cimg::temporary(out_val)=out_val):(*this)(x,y,z,v);
    }

    T atXYZ(const int x, const int y, const int z, const int v, const T out_val) const {
      return (x<0 || y<0 || z<0 || x>=dimx() || y>=dimy() || z>=dimz())?out_val:(*this)(x,y,z,v);
    }

    //! Read a pixel value with Neumann boundary conditions for the three first coordinates (\c x,\c y,\c z).
    T& atXYZ(const int x, const int y, const int z, const int v=0) {
      if (is_empty())
        throw CImgInstanceException("CImg<%s>::atXYZ() : Instance image is empty.",
                                    pixel_type());
      return _atXYZ(x,y,z,v);
    }

    T atXYZ(const int x, const int y, const int z, const int v=0) const {
      if (is_empty())
        throw CImgInstanceException("CImg<%s>::atXYZ() : Instance image is empty.",
                                    pixel_type());
      return _atXYZ(x,y,z,v);
    }

    T& _atXYZ(const int x, const int y, const int z, const int v=0) {
      return (*this)(x<0?0:(x>=dimx()?dimx()-1:x),y<0?0:(y>=dimy()?dimy()-1:y),
                     z<0?0:(z>=dimz()?dimz()-1:z),v);
    }

    T _atXYZ(const int x, const int y, const int z, const int v=0) const {
      return (*this)(x<0?0:(x>=dimx()?dimx()-1:x),y<0?0:(y>=dimy()?dimy()-1:y),
                     z<0?0:(z>=dimz()?dimz()-1:z),v);
    }

    //! Read a pixel value with Dirichlet boundary conditions for the two first coordinates (\c x,\c y).
    T& atXY(const int x, const int y, const int z, const int v, const T out_val) {
      return (x<0 || y<0 || x>=dimx() || y>=dimy())?(cimg::temporary(out_val)=out_val):(*this)(x,y,z,v);
    }

    T atXY(const int x, const int y, const int z, const int v, const T out_val) const {
      return (x<0 || y<0 || x>=dimx() || y>=dimy())?out_val:(*this)(x,y,z,v);
    }

    //! Read a pixel value with Neumann boundary conditions for the two first coordinates (\c x,\c y).
    T& atXY(const int x, const int y, const int z=0, const int v=0) {
      if (is_empty())
        throw CImgInstanceException("CImg<%s>::atXY() : Instance image is empty.",
                                    pixel_type());
      return _atXY(x,y,z,v);
    }

    T atXY(const int x, const int y, const int z=0, const int v=0) const {
      if (is_empty())
        throw CImgInstanceException("CImg<%s>::atXY() : Instance image is empty.",
                                    pixel_type());
      return _atXY(x,y,z,v);
    }

    T& _atXY(const int x, const int y, const int z=0, const int v=0) {
      return (*this)(x<0?0:(x>=dimx()?dimx()-1:x), y<0?0:(y>=dimy()?dimy()-1:y),z,v);
    }

    T _atXY(const int x, const int y, const int z=0, const int v=0) const {
      return (*this)(x<0?0:(x>=dimx()?dimx()-1:x), y<0?0:(y>=dimy()?dimy()-1:y),z,v);
    }

    //! Read a pixel value with Dirichlet boundary conditions for the first coordinates (\c x).
    T& atX(const int x, const int y, const int z, const int v, const T out_val) {
      return (x<0 || x>=dimx())?(cimg::temporary(out_val)=out_val):(*this)(x,y,z,v);
    }

    T atX(const int x, const int y, const int z, const int v, const T out_val) const {
      return (x<0 || x>=dimx())?out_val:(*this)(x,y,z,v);
    }

    //! Read a pixel value with Neumann boundary conditions for the first coordinates (\c x).
    T& atX(const int x, const int y=0, const int z=0, const int v=0) {
      if (is_empty())
        throw CImgInstanceException("CImg<%s>::atX() : Instance image is empty.",
                                    pixel_type());
      return _atX(x,y,z,v);
    }

    T atX(const int x, const int y=0, const int z=0, const int v=0) const {
      if (is_empty())
        throw CImgInstanceException("CImg<%s>::atX() : Instance image is empty.",
                                    pixel_type());
      return _atX(x,y,z,v);
    }

    T& _atX(const int x, const int y=0, const int z=0, const int v=0) {
      return (*this)(x<0?0:(x>=dimx()?dimx()-1:x),y,z,v);
    }

    T _atX(const int x, const int y=0, const int z=0, const int v=0) const {
      return (*this)(x<0?0:(x>=dimx()?dimx()-1:x),y,z,v);
    }

    //! Read a pixel value using linear interpolation and Dirichlet boundary conditions.
    Tfloat linear_atXYZV(const float fx, const float fy, const float fz, const float fv, const T out_val) const {
      const int
        x = (int)fx-(fx>=0?0:1), nx = x+1,
        y = (int)fy-(fy>=0?0:1), ny = y+1,
        z = (int)fz-(fz>=0?0:1), nz = z+1,
        v = (int)fv-(fv>=0?0:1), nv = v+1;
      const float
        dx = fx-x,
        dy = fy-y,
        dz = fz-z,
        dv = fv-v;
      const Tfloat
        Icccc = (Tfloat)atXYZV(x,y,z,v,out_val), Inccc = (Tfloat)atXYZV(nx,y,z,v,out_val),
        Icncc = (Tfloat)atXYZV(x,ny,z,v,out_val), Inncc = (Tfloat)atXYZV(nx,ny,z,v,out_val),
        Iccnc = (Tfloat)atXYZV(x,y,nz,v,out_val), Incnc = (Tfloat)atXYZV(nx,y,nz,v,out_val),
        Icnnc = (Tfloat)atXYZV(x,ny,nz,v,out_val), Innnc = (Tfloat)atXYZV(nx,ny,nz,v,out_val),
        Icccn = (Tfloat)atXYZV(x,y,z,nv,out_val), Inccn = (Tfloat)atXYZV(nx,y,z,nv,out_val),
        Icncn = (Tfloat)atXYZV(x,ny,z,nv,out_val), Inncn = (Tfloat)atXYZV(nx,ny,z,nv,out_val),
        Iccnn = (Tfloat)atXYZV(x,y,nz,nv,out_val), Incnn = (Tfloat)atXYZV(nx,y,nz,nv,out_val),
        Icnnn = (Tfloat)atXYZV(x,ny,nz,nv,out_val), Innnn = (Tfloat)atXYZV(nx,ny,nz,nv,out_val);
      return Icccc +
        dx*(Inccc-Icccc +
            dy*(Icccc+Inncc-Icncc-Inccc +
                dz*(Iccnc+Innnc+Icncc+Inccc-Icnnc-Incnc-Icccc-Inncc +
                    dv*(Iccnn+Innnn+Icncn+Inccn+Icnnc+Incnc+Icccc+Inncc-Icnnn-Incnn-Icccn-Inncn-Iccnc-Innnc-Icncc-Inccc)) +
                dv*(Icccn+Inncn+Icncc+Inccc-Icncn-Inccn-Icccc-Inncc)) +
            dz*(Icccc+Incnc-Iccnc-Inccc +
                dv*(Icccn+Incnn+Iccnc+Inccc-Iccnn-Inccn-Icccc-Incnc)) +
            dv*(Icccc+Inccn-Inccc-Icccn)) +
        dy*(Icncc-Icccc +
            dz*(Icccc+Icnnc-Iccnc-Icncc +
                dv*(Icccn+Icnnn+Iccnc+Icncc-Iccnn-Icncn-Icccc-Icnnc)) +
            dv*(Icccc+Icncn-Icncc-Icccn)) +
        dz*(Iccnc-Icccc +
            dv*(Icccc+Iccnn-Iccnc-Icccn)) +
        dv*(Icccn-Icccc);
    }

    //! Read a pixel value using linear interpolation and Neumann boundary conditions.
    Tfloat linear_atXYZV(const float fx, const float fy=0, const float fz=0, const float fv=0) const {
      if (is_empty())
        throw CImgInstanceException("CImg<%s>::linear_atXYZV() : Instance image is empty.",
                                    pixel_type());
      return _linear_atXYZV(fx,fy,fz,fv);
    }

    Tfloat _linear_atXYZV(const float fx, const float fy=0, const float fz=0, const float fv=0) const {
      const float
        nfx = fx<0?0:(fx>width-1?width-1:fx),
        nfy = fy<0?0:(fy>height-1?height-1:fy),
        nfz = fz<0?0:(fz>depth-1?depth-1:fz),
        nfv = fv<0?0:(fv>dim-1?dim-1:fv);
      const unsigned int
        x = (unsigned int)nfx,
        y = (unsigned int)nfy,
        z = (unsigned int)nfz,
        v = (unsigned int)nfv;
      const float
        dx = nfx-x,
        dy = nfy-y,
        dz = nfz-z,
        dv = nfv-v;
      const unsigned int
        nx = dx>0?x+1:x,
        ny = dy>0?y+1:y,
        nz = dz>0?z+1:z,
        nv = dv>0?v+1:v;
      const Tfloat
        Icccc = (Tfloat)(*this)(x,y,z,v), Inccc = (Tfloat)(*this)(nx,y,z,v),
        Icncc = (Tfloat)(*this)(x,ny,z,v), Inncc = (Tfloat)(*this)(nx,ny,z,v),
        Iccnc = (Tfloat)(*this)(x,y,nz,v), Incnc = (Tfloat)(*this)(nx,y,nz,v),
        Icnnc = (Tfloat)(*this)(x,ny,nz,v), Innnc = (Tfloat)(*this)(nx,ny,nz,v),
        Icccn = (Tfloat)(*this)(x,y,z,nv), Inccn = (Tfloat)(*this)(nx,y,z,nv),
        Icncn = (Tfloat)(*this)(x,ny,z,nv), Inncn = (Tfloat)(*this)(nx,ny,z,nv),
        Iccnn = (Tfloat)(*this)(x,y,nz,nv), Incnn = (Tfloat)(*this)(nx,y,nz,nv),
        Icnnn = (Tfloat)(*this)(x,ny,nz,nv), Innnn = (Tfloat)(*this)(nx,ny,nz,nv);
      return Icccc +
        dx*(Inccc-Icccc +
            dy*(Icccc+Inncc-Icncc-Inccc +
                dz*(Iccnc+Innnc+Icncc+Inccc-Icnnc-Incnc-Icccc-Inncc +
                    dv*(Iccnn+Innnn+Icncn+Inccn+Icnnc+Incnc+Icccc+Inncc-Icnnn-Incnn-Icccn-Inncn-Iccnc-Innnc-Icncc-Inccc)) +
                dv*(Icccn+Inncn+Icncc+Inccc-Icncn-Inccn-Icccc-Inncc)) +
            dz*(Icccc+Incnc-Iccnc-Inccc +
                dv*(Icccn+Incnn+Iccnc+Inccc-Iccnn-Inccn-Icccc-Incnc)) +
            dv*(Icccc+Inccn-Inccc-Icccn)) +
        dy*(Icncc-Icccc +
            dz*(Icccc+Icnnc-Iccnc-Icncc +
                dv*(Icccn+Icnnn+Iccnc+Icncc-Iccnn-Icncn-Icccc-Icnnc)) +
            dv*(Icccc+Icncn-Icncc-Icccn)) +
        dz*(Iccnc-Icccc +
            dv*(Icccc+Iccnn-Iccnc-Icccn)) +
        dv*(Icccn-Icccc);
    }

    //! Read a pixel value using linear interpolation and Dirichlet boundary conditions (first three coordinates).
    Tfloat linear_atXYZ(const float fx, const float fy, const float fz, const int v, const T out_val) const {
      const int
        x = (int)fx-(fx>=0?0:1), nx = x+1,
        y = (int)fy-(fy>=0?0:1), ny = y+1,
        z = (int)fz-(fz>=0?0:1), nz = z+1;
      const float
        dx = fx-x,
        dy = fy-y,
        dz = fz-z;
      const Tfloat
        Iccc = (Tfloat)atXYZ(x,y,z,v,out_val), Incc = (Tfloat)atXYZ(nx,y,z,v,out_val),
        Icnc = (Tfloat)atXYZ(x,ny,z,v,out_val), Innc = (Tfloat)atXYZ(nx,ny,z,v,out_val),
        Iccn = (Tfloat)atXYZ(x,y,nz,v,out_val), Incn = (Tfloat)atXYZ(nx,y,nz,v,out_val),
        Icnn = (Tfloat)atXYZ(x,ny,nz,v,out_val), Innn = (Tfloat)atXYZ(nx,ny,nz,v,out_val);
      return Iccc +
        dx*(Incc-Iccc +
            dy*(Iccc+Innc-Icnc-Incc +
                dz*(Iccn+Innn+Icnc+Incc-Icnn-Incn-Iccc-Innc)) +
            dz*(Iccc+Incn-Iccn-Incc)) +
        dy*(Icnc-Iccc +
            dz*(Iccc+Icnn-Iccn-Icnc)) +
        dz*(Iccn-Iccc);
    }

    //! Read a pixel value using linear interpolation and Neumann boundary conditions (first three coordinates).
    Tfloat linear_atXYZ(const float fx, const float fy=0, const float fz=0, const int v=0) const {
      if (is_empty())
        throw CImgInstanceException("CImg<%s>::linear_atXYZ() : Instance image is empty.",
                                    pixel_type());
      return _linear_atXYZ(fx,fy,fz,v);
    }

    Tfloat _linear_atXYZ(const float fx, const float fy=0, const float fz=0, const int v=0) const {
      const float
        nfx = fx<0?0:(fx>width-1?width-1:fx),
        nfy = fy<0?0:(fy>height-1?height-1:fy),
        nfz = fz<0?0:(fz>depth-1?depth-1:fz);
      const unsigned int
        x = (unsigned int)nfx,
        y = (unsigned int)nfy,
        z = (unsigned int)nfz;
      const float
        dx = nfx-x,
        dy = nfy-y,
        dz = nfz-z;
      const unsigned int
        nx = dx>0?x+1:x,
        ny = dy>0?y+1:y,
        nz = dz>0?z+1:z;
      const Tfloat
        Iccc = (Tfloat)(*this)(x,y,z,v), Incc = (Tfloat)(*this)(nx,y,z,v),
        Icnc = (Tfloat)(*this)(x,ny,z,v), Innc = (Tfloat)(*this)(nx,ny,z,v),
        Iccn = (Tfloat)(*this)(x,y,nz,v), Incn = (Tfloat)(*this)(nx,y,nz,v),
        Icnn = (Tfloat)(*this)(x,ny,nz,v), Innn = (Tfloat)(*this)(nx,ny,nz,v);
      return Iccc +
        dx*(Incc-Iccc +
            dy*(Iccc+Innc-Icnc-Incc +
                dz*(Iccn+Innn+Icnc+Incc-Icnn-Incn-Iccc-Innc)) +
            dz*(Iccc+Incn-Iccn-Incc)) +
        dy*(Icnc-Iccc +
            dz*(Iccc+Icnn-Iccn-Icnc)) +
        dz*(Iccn-Iccc);
    }

    //! Read a pixel value using linear interpolation and Dirichlet boundary conditions (first two coordinates).
    Tfloat linear_atXY(const float fx, const float fy, const int z, const int v, const T out_val) const {
      const int
        x = (int)fx-(fx>=0?0:1), nx = x+1,
        y = (int)fy-(fy>=0?0:1), ny = y+1;
      const float
        dx = fx-x,
        dy = fy-y;
      const Tfloat
        Icc = (Tfloat)atXY(x,y,z,v,out_val),  Inc = (Tfloat)atXY(nx,y,z,v,out_val),
        Icn = (Tfloat)atXY(x,ny,z,v,out_val), Inn = (Tfloat)atXY(nx,ny,z,v,out_val);
      return Icc + dx*(Inc-Icc + dy*(Icc+Inn-Icn-Inc)) + dy*(Icn-Icc);
    }

    //! Read a pixel value using linear interpolation and Neumann boundary conditions (first two coordinates).
    Tfloat linear_atXY(const float fx, const float fy, const int z=0, const int v=0) const {
      if (is_empty())
        throw CImgInstanceException("CImg<%s>::linear_atXY() : Instance image is empty.",
                                    pixel_type());
      return _linear_atXY(fx,fy,z,v);
    }

    Tfloat _linear_atXY(const float fx, const float fy, const int z=0, const int v=0) const {
      const float
        nfx = fx<0?0:(fx>width-1?width-1:fx),
        nfy = fy<0?0:(fy>height-1?height-1:fy);
      const unsigned int
        x = (unsigned int)nfx,
        y = (unsigned int)nfy;
      const float
        dx = nfx-x,
        dy = nfy-y;
      const unsigned int
        nx = dx>0?x+1:x,
        ny = dy>0?y+1:y;
      const Tfloat
        Icc = (Tfloat)(*this)(x,y,z,v),  Inc = (Tfloat)(*this)(nx,y,z,v),
        Icn = (Tfloat)(*this)(x,ny,z,v), Inn = (Tfloat)(*this)(nx,ny,z,v);
      return Icc + dx*(Inc-Icc + dy*(Icc+Inn-Icn-Inc)) + dy*(Icn-Icc);
    }

    //! Read a pixel value using linear interpolation and Dirichlet boundary conditions (first coordinate).
    Tfloat linear_atX(const float fx, const int y, const int z, const int v, const T out_val) const {
      const int
        x = (int)fx-(fx>=0?0:1), nx = x+1;
      const float
        dx = fx-x;
      const Tfloat
        Ic = (Tfloat)atX(x,y,z,v,out_val), In = (Tfloat)atXY(nx,y,z,v,out_val);
      return Ic + dx*(In-Ic);
    }

    //! Read a pixel value using linear interpolation and Neumann boundary conditions (first coordinate).
    Tfloat linear_atX(const float fx, const int y=0, const int z=0, const int v=0) const {
      if (is_empty())
        throw CImgInstanceException("CImg<%s>::linear_atX() : Instance image is empty.",
                                    pixel_type());
      return _linear_atX(fx,y,z,v);
    }

    Tfloat _linear_atX(const float fx, const int y=0, const int z=0, const int v=0) const {
      const float
        nfx = fx<0?0:(fx>width-1?width-1:fx);
      const unsigned int
        x = (unsigned int)nfx;
      const float
        dx = nfx-x;
      const unsigned int
        nx = dx>0?x+1:x;
      const Tfloat
        Ic = (Tfloat)(*this)(x,y,z,v), In = (Tfloat)(*this)(nx,y,z,v);
      return Ic + dx*(In-Ic);
    }

    //! Read a pixel value using cubic interpolation and Dirichlet boundary conditions.
    Tfloat cubic_atXY(const float fx, const float fy, const int z, const int v, const T out_val) const {
      const int
        x = (int)fx-(fx>=0?0:1), px = x-1, nx = x+1, ax = x+2,
        y = (int)fy-(fy>=0?0:1), py = y-1, ny = y+1, ay = y+2;
      const float
        dx = fx-x, dx2 = dx*dx, dx3 = dx2*dx,
        dy = fy-y;
      const Tfloat
        Ipp = (Tfloat)atXY(px,py,z,v,out_val), Icp = (Tfloat)atXY(x,py,z,v,out_val),
        Inp = (Tfloat)atXY(nx,py,z,v,out_val), Iap = (Tfloat)atXY(ax,py,z,v,out_val),
        Ipc = (Tfloat)atXY(px,y,z,v,out_val),  Icc = (Tfloat)atXY(x,y,z,v,out_val),
        Inc = (Tfloat)atXY(nx,y,z,v,out_val),  Iac = (Tfloat)atXY(ax,y,z,v,out_val),
        Ipn = (Tfloat)atXY(px,ny,z,v,out_val), Icn = (Tfloat)atXY(x,ny,z,v,out_val),
        Inn = (Tfloat)atXY(nx,ny,z,v,out_val), Ian = (Tfloat)atXY(ax,ny,z,v,out_val),
        Ipa = (Tfloat)atXY(px,ay,z,v,out_val), Ica = (Tfloat)atXY(x,ay,z,v,out_val),
        Ina = (Tfloat)atXY(nx,ay,z,v,out_val), Iaa = (Tfloat)atXY(ax,ay,z,v,out_val),
        valm = cimg::min(cimg::min(Ipp,Icp,Inp,Iap),cimg::min(Ipc,Icc,Inc,Iac),cimg::min(Ipn,Icn,Inn,Ian),cimg::min(Ipa,Ica,Ina,Iaa)),
        valM = cimg::max(cimg::max(Ipp,Icp,Inp,Iap),cimg::max(Ipc,Icc,Inc,Iac),cimg::max(Ipn,Icn,Inn,Ian),cimg::max(Ipa,Ica,Ina,Iaa)),
        u0p = Icp - Ipp,
        u1p = Iap - Inp,
        ap = 2*(Icp-Inp) + u0p + u1p,
        bp = 3*(Inp-Icp) - 2*u0p - u1p,
        u0c = Icc - Ipc,
        u1c = Iac - Inc,
        ac = 2*(Icc-Inc) + u0c + u1c,
        bc = 3*(Inc-Icc) - 2*u0c - u1c,
        u0n = Icn - Ipn,
        u1n = Ian - Inn,
        an = 2*(Icn-Inn) + u0n + u1n,
        bn = 3*(Inn-Icn) - 2*u0n - u1n,
        u0a = Ica - Ipa,
        u1a = Iaa - Ina,
        aa = 2*(Ica-Ina) + u0a + u1a,
        ba = 3*(Ina-Ica) - 2*u0a - u1a,
        valp = ap*dx3 + bp*dx2 + u0p*dx + Icp,
        valc = ac*dx3 + bc*dx2 + u0c*dx + Icc,
        valn = an*dx3 + bn*dx2 + u0n*dx + Icn,
        vala = aa*dx3 + ba*dx2 + u0a*dx + Ica,
        u0 = valc - valp,
        u1 = vala - valn,
        a = 2*(valc-valn) + u0 + u1,
        b = 3*(valn-valc) - 2*u0 - u1,
        val = a*dy*dy*dy + b*dy*dy + u0*dy + valc;
      return val<valm?valm:(val>valM?valM:val);
    }

    //! Read a pixel value using cubic interpolation and Neumann boundary conditions.
    Tfloat cubic_atXY(const float fx, const float fy, const int z=0, const int v=0) const {
      if (is_empty())
        throw CImgInstanceException("CImg<%s>::cubic_atXY() : Instance image is empty.",
                                    pixel_type());
      return _cubic_atXY(fx,fy,z,v);
    }

    Tfloat _cubic_atXY(const float fx, const float fy, const int z=0, const int v=0) const {
      const float
        nfx = fx<0?0:(fx>width-1?width-1:fx),
        nfy = fy<0?0:(fy>height-1?height-1:fy);
      const int
        x = (int)nfx,
        y = (int)nfy;
      const float
        dx = nfx-x, dx2 = dx*dx, dx3 = dx2*dx,
        dy = nfy-y;
      const int
        px = x-1<0?0:x-1, nx = dx>0?x+1:x, ax = x+2>=dimx()?dimx()-1:x+2,
        py = y-1<0?0:y-1, ny = dy>0?y+1:y, ay = y+2>=dimy()?dimy()-1:y+2;
      const Tfloat
        Ipp = (Tfloat)(*this)(px,py,z,v), Icp = (Tfloat)(*this)(x,py,z,v),
        Inp = (Tfloat)(*this)(nx,py,z,v), Iap = (Tfloat)(*this)(ax,py,z,v),
        Ipc = (Tfloat)(*this)(px,y,z,v),  Icc = (Tfloat)(*this)(x,y,z,v),
        Inc = (Tfloat)(*this)(nx,y,z,v),  Iac = (Tfloat)(*this)(ax,y,z,v),
        Ipn = (Tfloat)(*this)(px,ny,z,v), Icn = (Tfloat)(*this)(x,ny,z,v),
        Inn = (Tfloat)(*this)(nx,ny,z,v), Ian = (Tfloat)(*this)(ax,ny,z,v),
        Ipa = (Tfloat)(*this)(px,ay,z,v), Ica = (Tfloat)(*this)(x,ay,z,v),
        Ina = (Tfloat)(*this)(nx,ay,z,v), Iaa = (Tfloat)(*this)(ax,ay,z,v),
        valm = cimg::min(cimg::min(Ipp,Icp,Inp,Iap),cimg::min(Ipc,Icc,Inc,Iac),cimg::min(Ipn,Icn,Inn,Ian),cimg::min(Ipa,Ica,Ina,Iaa)),
        valM = cimg::max(cimg::max(Ipp,Icp,Inp,Iap),cimg::max(Ipc,Icc,Inc,Iac),cimg::max(Ipn,Icn,Inn,Ian),cimg::max(Ipa,Ica,Ina,Iaa)),
        u0p = Icp - Ipp,
        u1p = Iap - Inp,
        ap = 2*(Icp-Inp) + u0p + u1p,
        bp = 3*(Inp-Icp) - 2*u0p - u1p,
        u0c = Icc - Ipc,
        u1c = Iac - Inc,
        ac = 2*(Icc-Inc) + u0c + u1c,
        bc = 3*(Inc-Icc) - 2*u0c - u1c,
        u0n = Icn - Ipn,
        u1n = Ian - Inn,
        an = 2*(Icn-Inn) + u0n + u1n,
        bn = 3*(Inn-Icn) - 2*u0n - u1n,
        u0a = Ica - Ipa,
        u1a = Iaa - Ina,
        aa = 2*(Ica-Ina) + u0a + u1a,
        ba = 3*(Ina-Ica) - 2*u0a - u1a,
        valp = ap*dx3 + bp*dx2 + u0p*dx + Icp,
        valc = ac*dx3 + bc*dx2 + u0c*dx + Icc,
        valn = an*dx3 + bn*dx2 + u0n*dx + Icn,
        vala = aa*dx3 + ba*dx2 + u0a*dx + Ica,
        u0 = valc - valp,
        u1 = vala - valn,
        a = 2*(valc-valn) + u0 + u1,
        b = 3*(valn-valc) - 2*u0 - u1,
        val = a*dy*dy*dy + b*dy*dy + u0*dy + valc;
      return val<valm?valm:(val>valM?valM:val);
    }

    //! Read a pixel value using cubic interpolation and Dirichlet boundary conditions (first coordinates).
    Tfloat cubic_atX(const float fx, const int y, const int z, const int v, const T out_val) const {
      const int
        x = (int)fx-(fx>=0?0:1), px = x-1, nx = x+1, ax = x+2;
      const float
        dx = fx-x;
      const Tfloat
        Ip = (Tfloat)atX(px,y,z,v,out_val), Ic = (Tfloat)atX(x,y,z,v,out_val),
        In = (Tfloat)atX(nx,y,z,v,out_val), Ia = (Tfloat)atX(ax,y,z,v,out_val),
        valm = cimg::min(Ip,In,Ic,Ia), valM = cimg::max(Ip,In,Ic,Ia),
        u0 = Ic - Ip,
        u1 = Ia - In,
        a = 2*(Ic-In) + u0 + u1,
        b = 3*(In-Ic) - 2*u0 - u1,
        val = a*dx*dx*dx + b*dx*dx + u0*dx + Ic;
      return val<valm?valm:(val>valM?valM:val);
    }

    //! Read a pixel value using cubic interpolation and Neumann boundary conditions (first coordinates).
    Tfloat cubic_atX(const float fx, const int y=0, const int z=0, const int v=0) const {
      if (is_empty())
        throw CImgInstanceException("CImg<%s>::cubic_atX() : Instance image is empty.",
                                    pixel_type());
      return _cubic_atX(fx,y,z,v);
    }

    Tfloat _cubic_atX(const float fx, const int y=0, const int z=0, const int v=0) const {
      const float
        nfx = fx<0?0:(fx>width-1?width-1:fx);
      const int
        x = (int)nfx;
      const float
        dx = nfx-x;
      const int
        px = x-1<0?0:x-1, nx = dx>0?x+1:x, ax = x+2>=dimx()?dimx()-1:x+2;
      const Tfloat
        Ip = (Tfloat)(*this)(px,y,z,v), Ic = (Tfloat)(*this)(x,y,z,v),
        In = (Tfloat)(*this)(nx,y,z,v), Ia = (Tfloat)(*this)(ax,y,z,v),
        valm = cimg::min(Ip,In,Ic,Ia), valM = cimg::max(Ip,In,Ic,Ia),
        u0 = Ic - Ip,
        u1 = Ia - In,
        a = 2*(Ic-In) + u0 + u1,
        b = 3*(In-Ic) - 2*u0 - u1,
        val = a*dx*dx*dx + b*dx*dx + u0*dx + Ic;
      return val<valm?valm:(val>valM?valM:val);
    }

    //! Set a pixel value, with 3D float coordinates, using linear interpolation.
    CImg& set_linear_atXYZ(const T& val, const float fx, const float fy=0, const float fz=0, const int v=0,
                           const bool add=false) {
      const int
        x = (int)fx-(fx>=0?0:1), nx = x+1,
        y = (int)fy-(fy>=0?0:1), ny = y+1,
        z = (int)fz-(fz>=0?0:1), nz = z+1;
      const float
        dx = fx-x,
        dy = fy-y,
        dz = fz-z;
      if (v>=0 && v<dimv()) {
        if (z>=0 && z<dimz()) {
          if (y>=0 && y<dimy()) {
            if (x>=0 && x<dimx()) {
              const float w1 = (1-dx)*(1-dy)*(1-dz), w2 = add?1:(1-w1);
              (*this)(x,y,z,v) = (T)(w1*val + w2*(*this)(x,y,z,v));
            }
            if (nx>=0 && nx<dimx()) {
              const float w1 = dx*(1-dy)*(1-dz), w2 = add?1:(1-w1);
              (*this)(nx,y,z,v) = (T)(w1*val + w2*(*this)(nx,y,z,v));
            }
          }
          if (ny>=0 && ny<dimy()) {
            if (x>=0 && x<dimx()) {
              const float w1 = (1-dx)*dy*(1-dz), w2 = add?1:(1-w1);
              (*this)(x,ny,z,v) = (T)(w1*val + w2*(*this)(x,ny,z,v));
            }
            if (nx>=0 && nx<dimx()) {
              const float w1 = dx*dy*(1-dz), w2 = add?1:(1-w1);
              (*this)(nx,ny,z,v) = (T)(w1*val + w2*(*this)(nx,ny,z,v));
            }
          }
        }
        if (nz>=0 && nz<dimz()) {
          if (y>=0 && y<dimy()) {
            if (x>=0 && x<dimx()) {
              const float w1 = (1-dx)*(1-dy), w2 = add?1:(1-w1);
              (*this)(x,y,nz,v) = (T)(w1*val + w2*(*this)(x,y,nz,v));
            }
            if (nx>=0 && nx<dimx()) {
              const float w1 = dx*(1-dy), w2 = add?1:(1-w1);
              (*this)(nx,y,nz,v) = (T)(w1*val + w2*(*this)(nx,y,nz,v));
            }
          }
          if (ny>=0 && ny<dimy()) {
            if (x>=0 && x<dimx()) {
              const float w1 = (1-dx)*dy, w2 = add?1:(1-w1);
              (*this)(x,ny,nz,v) = (T)(w1*val + w2*(*this)(x,ny,nz,v));
            }
            if (nx>=0 && nx<dimx()) {
              const float w1 = dx*dy, w2 = add?1:(1-w1);
              (*this)(nx,ny,nz,v) = (T)(w1*val + w2*(*this)(nx,ny,nz,v));
            }
          }
        }
      }
      return *this;
    }

    //! Set a pixel value, with 2D float coordinates, using linear interpolation.
    CImg& set_linear_atXY(const T& val, const float fx, const float fy=0, const int z=0, const int v=0,
                          const bool add=false) {
      const int
        x = (int)fx-(fx>=0?0:1), nx = x+1,
        y = (int)fy-(fy>=0?0:1), ny = y+1;
      const float
        dx = fx-x,
        dy = fy-y;
      if (z>=0 && z<dimz() && v>=0 && v<dimv()) {
        if (y>=0 && y<dimy()) {
          if (x>=0 && x<dimx()) {
            const float w1 = (1-dx)*(1-dy), w2 = add?1:(1-w1);
            (*this)(x,y,z,v) = (T)(w1*val + w2*(*this)(x,y,z,v));
          }
          if (nx>=0 && nx<dimx()) {
            const float w1 = dx*(1-dy), w2 = add?1:(1-w1);
            (*this)(nx,y,z,v) = (T)(w1*val + w2*(*this)(nx,y,z,v));
          }
        }
        if (ny>=0 && ny<dimy()) {
          if (x>=0 && x<dimx()) {
            const float w1 = (1-dx)*dy, w2 = add?1:(1-w1);
            (*this)(x,ny,z,v) = (T)(w1*val + w2*(*this)(x,ny,z,v));
          }
          if (nx>=0 && nx<dimx()) {
            const float w1 = dx*dy, w2 = add?1:(1-w1);
            (*this)(nx,ny,z,v) = (T)(w1*val + w2*(*this)(nx,ny,z,v));
          }
        }
      }
      return *this;
    }

    //! Return a reference to the minimum pixel value of the instance image
    const T& min() const {
      if (is_empty())
        throw CImgInstanceException("CImg<%s>::min() : Instance image is empty.",
                                    pixel_type());
      const T *ptrmin = data;
      T min_value = *ptrmin;
      cimg_for(*this,ptr,T) if ((*ptr)<min_value) min_value = *(ptrmin=ptr);
      return *ptrmin;
    }

    //! Return a reference to the minimum pixel value of the instance image
    T& min() {
      if (is_empty())
        throw CImgInstanceException("CImg<%s>::min() : Instance image is empty.",
                                    pixel_type());
      T *ptrmin = data;
      T min_value = *ptrmin;
      cimg_for(*this,ptr,T) if ((*ptr)<min_value) min_value = *(ptrmin=ptr);
      return *ptrmin;
    }

    //! Return a reference to the maximum pixel value of the instance image
    const T& max() const {
      if (is_empty())
        throw CImgInstanceException("CImg<%s>::max() : Instance image is empty.",
                                    pixel_type());
      const T *ptrmax = data;
      T max_value = *ptrmax;
      cimg_for(*this,ptr,T) if ((*ptr)>max_value) max_value = *(ptrmax=ptr);
      return *ptrmax;
    }

    //! Return a reference to the maximum pixel value of the instance image
    T& max() {
      if (is_empty())
        throw CImgInstanceException("CImg<%s>::max() : Instance image is empty.",
                                    pixel_type());
      T *ptrmax = data;
      T max_value = *ptrmax;
      cimg_for(*this,ptr,T) if ((*ptr)>max_value) max_value = *(ptrmax=ptr);
      return *ptrmax;
    }

    //! Return a reference to the minimum pixel value and return also the maximum pixel value.
    template<typename t>
    const T& minmax(t& max_val) const {
      if (is_empty())
        throw CImgInstanceException("CImg<%s>::minmax() : Instance image is empty.",
                                    pixel_type());
      const T *ptrmin = data;
      T min_value = *ptrmin, max_value = min_value;
      cimg_for(*this,ptr,T) {
        const T val = *ptr;
        if (val<min_value) { min_value = val; ptrmin = ptr; }
        if (val>max_value) max_value = val;
      }
      max_val = (t)max_value;
      return *ptrmin;
    }

    //! Return a reference to the minimum pixel value and return also the maximum pixel value.
    template<typename t>
    T& minmax(t& max_val) {
      if (is_empty())
        throw CImgInstanceException("CImg<%s>::minmax() : Instance image is empty.",
                                    pixel_type());
      T *ptrmin = data;
      T min_value = *ptrmin, max_value = min_value;
      cimg_for(*this,ptr,T) {
        const T val = *ptr;
        if (val<min_value) { min_value = val; ptrmin = ptr; }
        if (val>max_value) max_value = val;
      }
      max_val = (t)max_value;
      return *ptrmin;
    }

    //! Return a reference to the maximum pixel value and return also the minimum pixel value.
    template<typename t>
    const T& maxmin(t& min_val) const {
      if (is_empty())
        throw CImgInstanceException("CImg<%s>::maxmin() : Instance image is empty.",
                                    pixel_type());
      const T *ptrmax = data;
      T max_value = *ptrmax, min_value = max_value;
      cimg_for(*this,ptr,T) {
        const T val = *ptr;
        if (val>max_value) { max_value = val; ptrmax = ptr; }
        if (val<min_value) min_value = val;
      }
      min_val = (t)min_value;
      return *ptrmax;
    }

    //! Return a reference to the maximum pixel value and return also the minimum pixel value.
    template<typename t>
    T& maxmin(t& min_val) {
      if (is_empty())
        throw CImgInstanceException("CImg<%s>::maxmin() : Instance image is empty.",
                                    pixel_type());
      T *ptrmax = data;
      T max_value = *ptrmax, min_value = max_value;
      cimg_for(*this,ptr,T) {
        const T val = *ptr;
        if (val>max_value) { max_value = val; ptrmax = ptr; }
        if (val<min_value) min_value = val;
      }
      min_val = (t)min_value;
      return *ptrmax;
    }

    //! Return the sum of all the pixel values in an image.
    Tfloat sum() const {
      if (is_empty())
        throw CImgInstanceException("CImg<%s>::sum() : Instance image (%u,%u,%u,%u,%p) is empty.",
                                                  pixel_type(),width,height,depth,dim,data);
      Tfloat res = 0;
      cimg_for(*this,ptr,T) res+=*ptr;
      return res;
    }

    //! Return the mean pixel value of the instance image.
    Tfloat mean() const {
      if (is_empty())
        throw CImgInstanceException("CImg<%s>::mean() : Instance image is empty.",
                                    pixel_type());
      Tfloat val = 0;
      cimg_for(*this,ptr,T) val+=*ptr;
      return val/size();
    }

    //! Return the variance of the image.
    /**
       @param variance_method Determines how to calculate the variance
       <table border="0">
       <tr><td>0</td>
       <td>Second moment:
       @f$ v = 1/N \sum\limits_{k=1}^{N} (x_k - \bar x)^2
       = 1/N \left( \sum\limits_{k=1}^N x_k^2 - \left( \sum\limits_{k=1}^N x_k \right)^2 / N \right) @f$
       with @f$ \bar x = 1/N \sum\limits_{k=1}^N x_k \f$</td></tr>
       <tr><td>1</td>
       <td>Best unbiased estimator: @f$ v = \frac{1}{N-1} \sum\limits_{k=1}^{N} (x_k - \bar x)^2 @f$</td></tr>
       <tr><td>2</td>
       <td>Least median of squares</td></tr>
       <tr><td>3</td>
       <td>Least trimmed of squares</td></tr>
       </table>
    */
    Tfloat variance(const unsigned int variance_method=1) const {
      Tfloat foo;
      return variancemean(variance_method,foo);
    }

    //! Return the variance and the mean of the image.
    template<typename t>
    Tfloat variancemean(const unsigned int variance_method, t& mean) const {
      if (is_empty())
        throw CImgInstanceException("CImg<%s>::variance() : Instance image is empty.",
                                    pixel_type());
      Tfloat variance = 0, average = 0;
      const unsigned int siz = size();
      switch (variance_method) {
      case 3 : { // Least trimmed of Squares
        CImg<Tfloat> buf(*this);
        const unsigned int siz2 = siz>>1;
        { cimg_for(buf,ptrs,Tfloat) { const Tfloat val = *ptrs; (*ptrs)*=val; average+=val; }}
        buf.sort();
        Tfloat a = 0;
        const Tfloat *ptrs = buf.ptr();
        for (unsigned int j = 0; j<siz2; ++j) a+=*(ptrs++);
        const Tfloat sig = (Tfloat)(2.6477*cimg_std::sqrt(a/siz2));
        variance = sig*sig;
      } break;
      case 2 : { // Least Median of Squares (MAD)
        CImg<Tfloat> buf(*this);
        buf.sort();
        const unsigned int siz2 = siz>>1;
        const Tfloat med_i = buf[siz2];
        cimg_for(buf,ptrs,Tfloat) { const Tfloat val = *ptrs; *ptrs = cimg::abs(val - med_i); average+=val; }
        buf.sort();
        const Tfloat sig = (Tfloat)(1.4828*buf[siz2]);
        variance = sig*sig;
      } break;
      case 1 : { // Least mean square (robust definition)
        Tfloat S = 0, S2 = 0;
        cimg_for(*this,ptr,T) { const Tfloat val = (Tfloat)*ptr; S+=val;  S2+=val*val; }
        variance = siz>1?(S2 - S*S/siz)/(siz - 1):0;
        average = S;
      } break;
      case 0 :{ // Least mean square (standard definition)
        Tfloat S = 0, S2 = 0;
        cimg_for(*this,ptr,T) { const Tfloat val = (Tfloat)*ptr; S+=val;  S2+=val*val; }
        variance = (S2 - S*S/siz)/siz;
        average = S;
      } break;
      default :
        throw CImgArgumentException("CImg<%s>::variancemean() : Incorrect parameter 'variance_method = %d' (correct values are 0,1,2 or 3).",
                                    pixel_type(),variance_method);
      }
      mean = (t)(average/siz);
      return variance>0?variance:0;
    }

    //! Return the kth smallest element of the image.
    // (Adapted from the numerical recipies for CImg)
    T kth_smallest(const unsigned int k) const {
      if (is_empty())
        throw CImgInstanceException("CImg<%s>::kth_smallest() : Instance image (%u,%u,%u,%u,%p) is empty.",
                                    pixel_type(),width,height,depth,dim,data);
      CImg<T> arr(*this);
      unsigned long l = 0, ir = size()-1;
      for (;;) {
        if (ir<=l+1) {
          if (ir==l+1 && arr[ir]<arr[l]) cimg::swap(arr[l],arr[ir]);
          return arr[k];
        } else {
          const unsigned long mid = (l+ir)>>1;
          cimg::swap(arr[mid],arr[l+1]);
          if (arr[l]>arr[ir]) cimg::swap(arr[l],arr[ir]);
          if (arr[l+1]>arr[ir]) cimg::swap(arr[l+1],arr[ir]);
          if (arr[l]>arr[l+1]) cimg::swap(arr[l],arr[l+1]);
          unsigned long i = l+1, j = ir;
          const T pivot = arr[l+1];
          for (;;) {
            do ++i; while (arr[i]<pivot);
            do --j; while (arr[j]>pivot);
            if (j<i) break;
            cimg::swap(arr[i],arr[j]);
          }
          arr[l+1] = arr[j];
          arr[j] = pivot;
          if (j>=k) ir=j-1;
          if (j<=k) l=i;
        }
      }
      return 0;
    }

    //! Compute a statistics vector (min,max,mean,variance,xmin,ymin,zmin,vmin,xmax,ymax,zmax,vmax).
    CImg<T>& stats(const unsigned int variance_method=1) {
      return get_stats(variance_method).transfer_to(*this);
    }

    CImg<Tfloat> get_stats(const unsigned int variance_method=1) const {
      if (is_empty()) return CImg<Tfloat>();
      const unsigned long siz = size();
      const T *const odata = data;
      const T *pm = odata, *pM = odata;
      Tfloat S = 0, S2 = 0;
      T m = *pm, M = m;
      cimg_for(*this,ptr,T) {
        const T val = *ptr;
        const Tfloat fval = (Tfloat)val;
        if (val<m) { m = val; pm = ptr; }
        if (val>M) { M = val; pM = ptr; }
        S+=fval;
        S2+=fval*fval;
      }
      const Tfloat
        mean_value = S/siz,
        _variance_value = variance_method==0?(S2 - S*S/siz)/siz:
        (variance_method==1?(siz>1?(S2 - S*S/siz)/(siz - 1):0):
         variance(variance_method)),
        variance_value = _variance_value>0?_variance_value:0;
      int
        xm = 0, ym = 0, zm = 0, vm = 0,
        xM = 0, yM = 0, zM = 0, vM = 0;
      contains(*pm,xm,ym,zm,vm);
      contains(*pM,xM,yM,zM,vM);
      return CImg<Tfloat>(1,12).fill((Tfloat)m,(Tfloat)M,mean_value,variance_value,
                                     (Tfloat)xm,(Tfloat)ym,(Tfloat)zm,(Tfloat)vm,
                                     (Tfloat)xM,(Tfloat)yM,(Tfloat)zM,(Tfloat)vM);
    }

    //! Return the median value of the image.
    T median() const {
      const unsigned int s = size();
      const T res = kth_smallest(s>>1);
      return (s%2)?res:((res+kth_smallest((s>>1)-1))/2);
    }

    //! Compute the MSE (Mean-Squared Error) between two images.
    template<typename t>
    Tfloat MSE(const CImg<t>& img) const {
      if (img.size()!=size())
        throw CImgArgumentException("CImg<%s>::MSE() : Instance image (%u,%u,%u,%u) and given image (%u,%u,%u,%u) have different dimensions.",
                                    pixel_type(),width,height,depth,dim,img.width,img.height,img.depth,img.dim);

      Tfloat vMSE = 0;
      const t* ptr2 = img.end();
      cimg_for(*this,ptr1,T) {
        const Tfloat diff = (Tfloat)*ptr1 - (Tfloat)*(--ptr2);
        vMSE += diff*diff;
      }
      vMSE/=img.size();
      return vMSE;
    }

    //! Compute the PSNR between two images.
    template<typename t>
    Tfloat PSNR(const CImg<t>& img, const Tfloat valmax=(Tfloat)255) const {
      const Tfloat vMSE = (Tfloat)cimg_std::sqrt(MSE(img));
      return (vMSE!=0)?(Tfloat)(20*cimg_std::log10(valmax/vMSE)):(Tfloat)(cimg::type<Tfloat>::max());
    }

    //! Return the trace of the image, viewed as a matrix.
    Tfloat trace() const {
      if (is_empty())
        throw CImgInstanceException("CImg<%s>::trace() : Instance matrix (%u,%u,%u,%u,%p) is empty.",
                                    pixel_type(),width,height,depth,dim,data);
      Tfloat res = 0;
      cimg_forX(*this,k) res+=(*this)(k,k);
      return res;
    }

    //! Return the dot product of the current vector/matrix with the vector/matrix \p img.
    template<typename t>
    Tfloat dot(const CImg<t>& img) const {
      if (is_empty())
        throw CImgInstanceException("CImg<%s>::dot() : Instance object (%u,%u,%u,%u,%p) is empty.",
                                    pixel_type(),width,height,depth,dim,data);
      if (!img)
        throw CImgArgumentException("CImg<%s>::trace() : Specified argument (%u,%u,%u,%u,%p) is empty.",
                                    pixel_type(),img.width,img.height,img.depth,img.dim,img.data);
      const unsigned long nb = cimg::min(size(),img.size());
      Tfloat res = 0;
      for (unsigned long off = 0; off<nb; ++off) res+=(Tfloat)data[off]*(Tfloat)img[off];
      return res;
    }

    //! Return the determinant of the image, viewed as a matrix.
    Tfloat det() const {
      if (is_empty() || width!=height || depth!=1 || dim!=1)
        throw CImgInstanceException("CImg<%s>::det() : Instance matrix (%u,%u,%u,%u,%p) is not square or is empty.",
                                    pixel_type(),width,height,depth,dim,data);
      switch (width) {
      case 1 : return (Tfloat)((*this)(0,0));
      case 2 : return (Tfloat)((*this)(0,0))*(Tfloat)((*this)(1,1)) - (Tfloat)((*this)(0,1))*(Tfloat)((*this)(1,0));
      case 3 : {
        const Tfloat
          a = (Tfloat)data[0], d = (Tfloat)data[1], g = (Tfloat)data[2],
          b = (Tfloat)data[3], e = (Tfloat)data[4], h = (Tfloat)data[5],
          c = (Tfloat)data[6], f = (Tfloat)data[7], i = (Tfloat)data[8];
        return i*a*e - a*h*f - i*b*d + b*g*f + c*d*h - c*g*e;
      }
      default : {
        CImg<Tfloat> lu(*this);
        CImg<uintT> indx;
        bool d;
        lu._LU(indx,d);
        Tfloat res = d?(Tfloat)1:(Tfloat)-1;
        cimg_forX(lu,i) res*=lu(i,i);
        return res;
      }
      }
      return 0;
    }

    //! Return the norm of the current vector/matrix. \p ntype = norm type (0=L2, 1=L1, -1=Linf).
    Tfloat norm(const int norm_type=2) const {
      if (is_empty())
        throw CImgInstanceException("CImg<%s>::norm() : Instance object (%u,%u,%u,%u,%p) is empty.",
                                    pixel_type(),width,height,depth,dim,data);
      Tfloat res = 0;
      switch (norm_type) {
      case -1 : {
        cimg_foroff(*this,off) {
          const Tfloat tmp = cimg::abs((Tfloat)data[off]);
          if (tmp>res) res = tmp;
        }
        return res;
      } break;
      case 1 : {
        cimg_foroff(*this,off) res+=cimg::abs((Tfloat)data[off]);
        return res;
      } break;
      case 2 : return (Tfloat)cimg_std::sqrt(dot(*this)); break;
      default :
        throw CImgArgumentException("CImg<%s>::norm() : Incorrect parameter 'norm_type=%d' (correct values are -1,1 or 2).",
                                    pixel_type(),norm_type);
      }
      return 0;
    }

    //! Return a C-string containing the values of the instance image.
    CImg<charT> value_string(const char separator=',', const unsigned int max_size=0) const {
      if (is_empty()) return CImg<charT>(1,1,1,1,0);
      const unsigned int siz = (unsigned int)size();
      CImgList<charT> items;
      char item[256] = { 0 };
      const T *ptrs = ptr();
      for (unsigned int off = 0; off<siz-1; ++off) {
        cimg_std::sprintf(item,cimg::type<T>::format(),cimg::type<T>::format(*(ptrs++)));
        const int l = cimg::strlen(item);
        items.insert(CImg<charT>(item,l+1));
        items[items.size-1](l) = separator;
      }
      cimg_std::sprintf(item,cimg::type<T>::format(),cimg::type<T>::format(*ptrs));
      items.insert(CImg<charT>(item,cimg::strlen(item)+1));
      CImg<ucharT> res = items.get_append('x');
      if (max_size) { res.crop(0,max_size); res(max_size) = 0; }
      return res;
    }

    //! Display informations about the image on the standard error output.
    /**
       \param title Name for the considered image (optional).
       \param display_stats Compute and display image statistics (optional).
    **/
    const CImg<T>& print(const char *title=0, const bool display_stats=true) const {
      int xm = 0, ym = 0, zm = 0, vm = 0, xM = 0, yM = 0, zM = 0, vM = 0;
      static CImg<doubleT> st;
      if (!is_empty() && display_stats) {
        st = get_stats();
        xm = (int)st[4]; ym = (int)st[5], zm = (int)st[6], vm = (int)st[7];
        xM = (int)st[8]; yM = (int)st[9], zM = (int)st[10], vM = (int)st[11];
      }
      const unsigned long siz = size(), msiz = siz*sizeof(T), siz1 = siz-1;
      const unsigned int mdisp = msiz<8*1024?0:(msiz<8*1024*1024?1:2), width1 = width-1;
      char ntitle[64] = { 0 };
      if (!title) cimg_std::sprintf(ntitle,"CImg<%s>",pixel_type());
      cimg_std::fprintf(cimg_stdout,"%s: this = %p, size = (%u,%u,%u,%u) [%lu %s], data = (%s*)%p (%s) = [ ",
                   title?title:ntitle,(void*)this,width,height,depth,dim,
                   mdisp==0?msiz:(mdisp==1?(msiz>>10):(msiz>>20)),
                   mdisp==0?"b":(mdisp==1?"Kb":"Mb"),
                   pixel_type(),(void*)data,is_shared?"shared":"not shared");
      if (!is_empty()) cimg_foroff(*this,off) {
        cimg_std::fprintf(cimg_stdout,cimg::type<T>::format(),cimg::type<T>::format(data[off]));
        if (off!=siz1) cimg_std::fprintf(cimg_stdout,"%s",off%width==width1?" ; ":" ");
        if (off==7 && siz>16) { off = siz1-8; if (off!=7) cimg_std::fprintf(cimg_stdout,"... "); }
      }
      if (!is_empty() && display_stats)
        cimg_std::fprintf(cimg_stdout," ], min = %g, max = %g, mean = %g, std = %g, coords(min) = (%u,%u,%u,%u), coords(max) = (%u,%u,%u,%u).\n",
                     st[0],st[1],st[2],cimg_std::sqrt(st[3]),xm,ym,zm,vm,xM,yM,zM,vM);
      else cimg_std::fprintf(cimg_stdout,"%s].\n",is_empty()?"":" ");
      return *this;
    }

    //@}
    //------------------------------------------
    //
    //! \name Arithmetic and Boolean Operators
    //@{
    //------------------------------------------

    //! Assignment operator.
    /**
       This operator assigns a copy of the input image \p img to the current instance image.
       \param img The input image to copy.
       \remark
       - This operator is strictly equivalent to the function assign(const CImg< t >&) and has exactly the same properties.
    **/
    template<typename t>
    CImg<T>& operator=(const CImg<t>& img) {
      return assign(img);
    }

    CImg<T>& operator=(const CImg<T>& img) {
      return assign(img);
    }

    //! Assign values of a C-array to the instance image.
    /**
       \param buf Pointer to a C-style array having a size of (at least) <tt>this->size()</tt>.

       - Replace pixel values by the content of the array \c buf.
       - Warning : the value types in the array and in the image must be the same.

       \par example:
       \code
       float tab[4*4] = { 1,2,3,4, 5,6,7,8, 9,10,11,12, 13,14,15,16 };  // Define a 4x4 matrix in C-style.
       CImg<float> matrice(4,4);                                        // Define a 4x4 greyscale image.
       matrice = tab;                                                   // Fill the image by the values in tab.
       \endcode
    **/
    CImg<T>& operator=(const T *buf) {
      return assign(buf,width,height,depth,dim);
    }

    //! Assign a value to each image pixel of the instance image.
    CImg<T>& operator=(const T val) {
      return fill(val);
    }

    //! Operator+
    /**
       \remark
       - This operator can be used to get a non-shared copy of an image.
    **/
    CImg<T> operator+() const {
      return CImg<T>(*this,false);
    }

    //! Operator+=;
#ifdef cimg_use_visualcpp6
    CImg<T>& operator+=(const T val)
#else
    template<typename t>
    CImg<T>& operator+=(const t val)
#endif
    {
      cimg_for(*this,ptr,T) (*ptr) = (T)((*ptr)+val);
      return *this;
    }

    //! Operator+=
    template<typename t>
    CImg<T>& operator+=(const CImg<t>& img) {
      if (is_overlapped(img)) return *this+=+img;
      const unsigned int smin = cimg::min(size(),img.size());
      t *ptrs = img.data + smin;
      for (T *ptrd = data + smin; ptrd>data; --ptrd, (*ptrd)=(T)((*ptrd)+(*(--ptrs)))) {}
      return *this;
    }

    //! Operator++ (prefix)
    CImg<T>& operator++() {
      cimg_for(*this,ptr,T) ++(*ptr);
      return *this;
    }

    //! Operator++ (postfix)
    CImg<T> operator++(int) {
      const CImg<T> copy(*this,false);
      ++*this;
      return copy;
    }

    //! Operator-.
    CImg<T> operator-() const {
      return CImg<T>(width,height,depth,dim,0)-=*this;
    }

    //! Operator-=.
#ifdef cimg_use_visualcpp6
    CImg<T>& operator-=(const T val)
#else
    template<typename t>
    CImg<T>& operator-=(const t val)
#endif
    {
      cimg_for(*this,ptr,T) (*ptr) = (T)((*ptr)-val);
      return *this;
    }

    //! Operator-=.
    template<typename t>
    CImg<T>& operator-=(const CImg<t>& img) {
      if (is_overlapped(img)) return *this-=+img;
      const unsigned int smin = cimg::min(size(),img.size());
      t *ptrs = img.data+smin;
      for (T *ptrd = data+smin; ptrd>data; --ptrd, (*ptrd) = (T)((*ptrd)-(*(--ptrs)))) {}
      return *this;
    }

    //! Operator-- (prefix).
    CImg<T>& operator--() {
      cimg_for(*this,ptr,T) *ptr = *ptr-(T)1;
      return *this;
    }

    //! Operator-- (postfix).
    CImg<T> operator--(int) {
      CImg<T> copy(*this,false);
      --*this;
      return copy;
    }

    //! Operator*=.
#ifdef cimg_use_visualcpp6
    CImg<T>& operator*=(const double val)
#else
    template<typename t>
    CImg<T>& operator*=(const t val)
#endif
    {
      cimg_for(*this,ptr,T) (*ptr) = (T)((*ptr)*val);
      return *this;
    }

    //! Operator*=.
    template<typename t>
    CImg<T>& operator*=(const CImg<t>& img) {
      return ((*this)*img).transfer_to(*this);
    }

    //! Operator/=.
#ifdef cimg_use_visualcpp6
    CImg<T>& operator/=(const double val)
#else
    template<typename t>
    CImg<T>& operator/=(const t val)
#endif
    {
      cimg_for(*this,ptr,T) (*ptr) = (T)((*ptr)/val);
      return *this;
    }

    //! Operator/=.
    template<typename t>
    CImg<T>& operator/=(const CImg<t>& img) {
      return assign(*this*img.get_invert());
    }

    //! Modulo.
    template<typename t>
    CImg<typename cimg::superset<T,t>::type> operator%(const CImg<t>& img) const {
      typedef typename cimg::superset<T,t>::type Tt;
      return CImg<Tt>(*this,false)%=img;
    }

    //! Modulo.
    CImg<T> operator%(const T val) const {
      return (+*this)%=val;
    }

    //! In-place modulo.
    CImg<T>& operator%=(const T val) {
      cimg_for(*this,ptr,T) (*ptr) = (T)cimg::mod(*ptr,val);
      return *this;
    }

    //! In-place modulo.
    template<typename t>
    CImg<T>& operator%=(const CImg<t>& img) {
      if (is_overlapped(img)) return *this%=+img;
      typedef typename cimg::superset<T,t>::type Tt;
      const unsigned int smin = cimg::min(size(),img.size());
      const t *ptrs = img.data + smin;
      for (T *ptrd = data + smin; ptrd>data; ) {
        T& val = *(--ptrd);
        val = (T)cimg::mod((Tt)val,(Tt)*(--ptrs));
      }
      return *this;
    }

    //! Bitwise AND.
    template<typename t>
    CImg<typename cimg::superset<T,t>::type> operator&(const CImg<t>& img) const {
      typedef typename cimg::superset<T,t>::type Tt;
      return CImg<Tt>(*this,false)&=img;
    }

    //! Bitwise AND.
    CImg<T> operator&(const T val) const {
      return (+*this)&=val;
    }

    //! In-place bitwise AND.
    template<typename t>
    CImg<T>& operator&=(const CImg<t>& img) {
      if (is_overlapped(img)) return *this&=+img;
      const unsigned int smin = cimg::min(size(),img.size());
      const t *ptrs = img.data + smin;
      for (T *ptrd = data + smin; ptrd>data; ) {
        T& val = *(--ptrd);
        val = (T)((unsigned long)val & (unsigned long)*(--ptrs));
      }
      return *this;
    }

    //! In-place bitwise AND.
    CImg<T>& operator&=(const T val) {
      cimg_for(*this,ptr,T) *ptr = (T)((unsigned long)*ptr & (unsigned long)val);
      return *this;
    }

    //! Bitwise OR.
    template<typename t>
    CImg<typename cimg::superset<T,t>::type> operator|(const CImg<t>& img) const {
      typedef typename cimg::superset<T,t>::type Tt;
      return CImg<Tt>(*this,false)|=img;
    }

    //! Bitwise OR.
    CImg<T> operator|(const T val) const {
      return (+*this)|=val;
    }

    //! In-place bitwise OR.
    template<typename t>
    CImg<T>& operator|=(const CImg<t>& img) {
      if (is_overlapped(img)) return *this|=+img;
      const unsigned int smin = cimg::min(size(),img.size());
      const t *ptrs = img.data + smin;
      for (T *ptrd = data + smin; ptrd>data; ) {
        T& val = *(--ptrd);
        val = (T)((unsigned long)val | (unsigned long)*(--ptrs));
      }
      return *this;
    }

    //! In-place bitwise OR.
    CImg<T>& operator|=(const T val) {
      cimg_for(*this,ptr,T) *ptr = (T)((unsigned long)*ptr | (unsigned long)val);
      return *this;
    }

    //! Bitwise XOR.
    template<typename t>
    CImg<typename cimg::superset<T,t>::type> operator^(const CImg<t>& img) const {
      typedef typename cimg::superset<T,t>::type Tt;
      return CImg<Tt>(*this,false)^=img;
    }

    //! Bitwise XOR.
    CImg<T> operator^(const T val) const {
      return (+*this)^=val;
    }

    //! In-place bitwise XOR.
    template<typename t>
    CImg<T>& operator^=(const CImg<t>& img) {
      if (is_overlapped(img)) return *this^=+img;
      const unsigned int smin = cimg::min(size(),img.size());
      const t *ptrs = img.data + smin;
      for (T *ptrd = data+smin; ptrd>data; ) {
        T& val = *(--ptrd);
        val =(T)((unsigned long)val ^ (unsigned long)*(--ptrs));
      }
      return *this;
    }

    //! In-place bitwise XOR.
    CImg<T>& operator^=(const T val) {
      cimg_for(*this,ptr,T) *ptr = (T)((unsigned long)*ptr ^ (unsigned long)val);
      return *this;
    }

    //! Bitwise NOT.
    CImg<T> operator~() const {
      CImg<T> res(width,height,depth,dim);
      const T *ptrs = end();
      cimg_for(res,ptrd,T) { const unsigned long val = (unsigned long)*(--ptrs); *ptrd = (T)~val; }
      return res;
    }

    //! Bitwise left shift.
    CImg<T>& operator<<=(const int n) {
      cimg_for(*this,ptr,T) *ptr = (T)(((long)*ptr)<<n);
      return *this;
    }

    //! Bitwise left shift.
    CImg<T> operator<<(const int n) const {
      return (+*this)<<=n;
    }

    //! Bitwise right shift.
    CImg<T>& operator>>=(const int n) {
      cimg_for(*this,ptr,T) *ptr = (T)(((long)*ptr)>>n);
      return *this;
    }

    //! Bitwise right shift.
    CImg<T> operator>>(const int n) const {
      return (+*this)>>=n;
    }

    //! Boolean equality.
    template<typename t>
    bool operator==(const CImg<t>& img) const {
      const unsigned int siz = size();
      bool vequal = true;
      if (siz!=img.size()) return false;
      t *ptrs = img.data + siz;
      for (T *ptrd = data + siz; vequal && ptrd>data; vequal = vequal && ((*(--ptrd))==(*(--ptrs)))) {}
      return vequal;
    }

    //! Boolean difference.
    template<typename t>
    bool operator!=(const CImg<t>& img) const {
      return !((*this)==img);
    }

    //! Return a list of two images { *this, img }.
    template<typename t>
    CImgList<typename cimg::superset<T,t>::type> operator<<(const CImg<t>& img) const {
      typedef typename cimg::superset<T,t>::type Tt;
      return CImgList<Tt>(*this,img);
    }

    //! Return a copy of \p list, where image *this has been inserted at first position.
    template<typename t>
    CImgList<typename cimg::superset<T,t>::type> operator<<(const CImgList<t>& list) const {
      typedef typename cimg::superset<T,t>::type Tt;
      return CImgList<Tt>(list).insert(*this,0);
    }

    //! Return a list of two images { *this, img }.
    template<typename t>
    CImgList<typename cimg::superset<T,t>::type> operator>>(const CImg<t>& img) const {
      return (*this)<<img;
    }

    //! Insert an image into the begining of an image list.
    template<typename t>
    CImgList<t>& operator>>(const CImgList<t>& list) const {
      return list.insert(*this,0);
    }

    //! Display an image into a CImgDisplay.
    const CImg<T>& operator>>(CImgDisplay& disp) const {
      return display(disp);
    }

    //@}
    //---------------------------------------
    //
    //! \name Usual Mathematics Functions
    //@{
    //---------------------------------------

    //! Apply a R->R function on all pixel values.
    template<typename t>
    CImg<T>& apply(t& func) {
      cimg_for(*this,ptr,T) *ptr = func(*ptr);
      return *this;
    }

    template<typename t>
    CImg<T> get_apply(t& func) const {
      return (+*this).apply(func);
    }

    //! Pointwise multiplication between two images.
    template<typename t>
    CImg<T>& mul(const CImg<t>& img) {
      if (is_overlapped(img)) return mul(+img);
      t *ptrs = img.data;
      T *ptrf = data + cimg::min(size(),img.size());
      for (T* ptrd = data; ptrd<ptrf; ++ptrd) (*ptrd) = (T)(*ptrd*(*(ptrs++)));
      return *this;
    }

    template<typename t>
    CImg<typename cimg::superset<T,t>::type> get_mul(const CImg<t>& img) const {
      typedef typename cimg::superset<T,t>::type Tt;
      return CImg<Tt>(*this,false).mul(img);
    }

    //! Pointwise division between two images.
    template<typename t>
    CImg<T>& div(const CImg<t>& img) {
      if (is_overlapped(img)) return div(+img);
      t *ptrs = img.data;
      T *ptrf = data + cimg::min(size(),img.size());
      for (T* ptrd = data; ptrd<ptrf; ++ptrd) (*ptrd) = (T)(*ptrd/(*(ptrs++)));
      return *this;
    }

    template<typename t>
    CImg<typename cimg::superset<T,t>::type> get_div(const CImg<t>& img) const {
      typedef typename cimg::superset<T,t>::type Tt;
      return CImg<Tt>(*this,false).div(img);
    }

    //! Pointwise max operator between two images.
    template<typename t>
    CImg<T>& max(const CImg<t>& img) {
      if (is_overlapped(img)) return max(+img);
      t *ptrs = img.data;
      T *ptrf = data + cimg::min(size(),img.size());
      for (T* ptrd = data; ptrd<ptrf; ++ptrd) (*ptrd) = cimg::max((T)*(ptrs++),*ptrd);
      return *this;
    }

    template<typename t>
    CImg<typename cimg::superset<T,t>::type> get_max(const CImg<t>& img) const {
      typedef typename cimg::superset<T,t>::type Tt;
      return CImg<Tt>(*this,false).max(img);
    }

    //! Pointwise max operator between an image and a value.
    CImg<T>& max(const T val) {
      cimg_for(*this,ptr,T) (*ptr) = cimg::max(*ptr,val);
      return *this;
    }

    CImg<T> get_max(const T val) const {
      return (+*this).max(val);
    }

    //! Pointwise min operator between two images.
    template<typename t>
    CImg<T>& min(const CImg<t>& img) {
      if (is_overlapped(img)) return min(+img);
      t *ptrs = img.data;
      T *ptrf = data + cimg::min(size(),img.size());
      for (T* ptrd = data; ptrd<ptrf; ++ptrd) (*ptrd) = cimg::min((T)*(ptrs++),*ptrd);
      return *this;
    }

    template<typename t>
    CImg<typename cimg::superset<T,t>::type> get_min(const CImg<t>& img) const {
      typedef typename cimg::superset<T,t>::type Tt;
      return CImg<Tt>(*this,false).min(img);
    }

    //! Pointwise min operator between an image and a value.
    CImg<T>& min(const T val) {
      cimg_for(*this,ptr,T) (*ptr) = cimg::min(*ptr,val);
      return *this;
    }

    CImg<T> get_min(const T val) const {
      return (+*this).min(val);
    }

    //! Compute the square value of each pixel.
    CImg<T>& sqr() {
      cimg_for(*this,ptr,T) { const T val = *ptr; *ptr = (T)(val*val); };
      return *this;
    }

    CImg<Tfloat> get_sqr() const {
      return CImg<Tfloat>(*this,false).sqr();
    }

    //! Compute the square root of each pixel value.
    CImg<T>& sqrt() {
      cimg_for(*this,ptr,T) (*ptr) = (T)cimg_std::sqrt((double)(*ptr));
      return *this;
    }

    CImg<Tfloat> get_sqrt() const {
      return CImg<Tfloat>(*this,false).sqrt();
    }

    //! Compute the exponential of each pixel value.
    CImg<T>& exp() {
      cimg_for(*this,ptr,T) (*ptr) = (T)cimg_std::exp((double)(*ptr));
      return *this;
    }

    CImg<Tfloat> get_exp() const {
      return CImg<Tfloat>(*this,false).exp();
    }

    //! Compute the log of each pixel value.
    CImg<T>& log() {
      cimg_for(*this,ptr,T) (*ptr) = (T)cimg_std::log((double)(*ptr));
      return *this;
    }

    CImg<Tfloat> get_log() const {
      return CImg<Tfloat>(*this,false).log();
    }

    //! Compute the log10 of each pixel value.
    CImg<T>& log10() {
      cimg_for(*this,ptr,T) (*ptr) = (T)cimg_std::log10((double)(*ptr));
      return *this;
    }

    CImg<Tfloat> get_log10() const {
      return CImg<Tfloat>(*this,false).log10();
    }

    //! Compute the power by p of each pixel value.
    CImg<T>& pow(const double p) {
      if (p==0) return fill(1);
      if (p==0.5) { cimg_for(*this,ptr,T) { const T val = *ptr; *ptr = (T)cimg_std::sqrt((double)val); } return *this; }
      if (p==1) return *this;
      if (p==2) { cimg_for(*this,ptr,T) { const T val = *ptr; *ptr = val*val; } return *this; }
      if (p==3) { cimg_for(*this,ptr,T) { const T val = *ptr; *ptr = val*val*val; } return *this; }
      if (p==4) { cimg_for(*this,ptr,T) { const T val = *ptr; *ptr = val*val*val*val; } return *this; }
      cimg_for(*this,ptr,T) (*ptr) = (T)cimg_std::pow((double)(*ptr),p);
      return *this;
    }

    CImg<Tfloat> get_pow(const double p) const {
      return CImg<Tfloat>(*this,false).pow(p);
    }

    //! Compute the power of each pixel value.
    template<typename t>
    CImg<T>& pow(const CImg<t>& img) {
      if (is_overlapped(img)) return pow(+img);
      t *ptrs = img.data;
      T *ptrf = data + cimg::min(size(),img.size());
      for (T* ptrd = data; ptrd<ptrf; ++ptrd) (*ptrd) = (T)cimg_std::pow((double)*ptrd,(double)(*(ptrs++)));
      return *this;
    }

    template<typename t>
    CImg<Tfloat> get_pow(const CImg<t>& img) const {
      return CImg<Tfloat>(*this,false).pow(img);
    }

    //! Compute the absolute value of each pixel value.
    CImg<T>& abs() {
      cimg_for(*this,ptr,T) (*ptr) = cimg::abs(*ptr);
      return *this;
    }

    CImg<Tfloat> get_abs() const {
      return CImg<Tfloat>(*this,false).abs();
    }

    //! Compute the cosinus of each pixel value.
    CImg<T>& cos() {
      cimg_for(*this,ptr,T) (*ptr) = (T)cimg_std::cos((double)(*ptr));
      return *this;
    }

    CImg<Tfloat> get_cos() const {
      return CImg<Tfloat>(*this,false).cos();
    }

    //! Compute the sinus of each pixel value.
    CImg<T>& sin() {
      cimg_for(*this,ptr,T) (*ptr) = (T)cimg_std::sin((double)(*ptr));
      return *this;
    }

    CImg<Tfloat> get_sin() const {
      return CImg<Tfloat>(*this,false).sin();
    }

    //! Compute the tangent of each pixel.
    CImg<T>& tan() {
      cimg_for(*this,ptr,T) (*ptr) = (T)cimg_std::tan((double)(*ptr));
      return *this;
    }

    CImg<Tfloat> get_tan() const {
      return CImg<Tfloat>(*this,false).tan();
    }

    //! Compute the arc-cosine of each pixel value.
    CImg<T>& acos() {
      cimg_for(*this,ptr,T) (*ptr) = (T)cimg_std::acos((double)(*ptr));
      return *this;
    }

    CImg<Tfloat> get_acos() const {
      return CImg<Tfloat>(*this,false).acos();
    }

    //! Compute the arc-sinus of each pixel value.
    CImg<T>& asin() {
      cimg_for(*this,ptr,T) (*ptr) = (T)cimg_std::asin((double)(*ptr));
      return *this;
    }

    CImg<Tfloat> get_asin() const {
      return CImg<Tfloat>(*this,false).asin();
    }

    //! Compute the arc-tangent of each pixel.
    CImg<T>& atan() {
      cimg_for(*this,ptr,T) (*ptr) = (T)cimg_std::atan((double)(*ptr));
      return *this;
    }

    CImg<Tfloat> get_atan() const {
      return CImg<Tfloat>(*this,false).atan();
    }

    //! Compute image with rounded pixel values.
    /**
       \param x Rounding precision.
       \param rounding_type Roundin type, can be 0 (nearest), 1 (forward), -1(backward).
    **/
    CImg<T>& round(const float x, const int rounding_type=0) {
      cimg_for(*this,ptr,T) (*ptr) = (T)cimg::round(*ptr,x,rounding_type);
      return *this;
    }

    CImg<T> get_round(const float x, const unsigned int rounding_type=0) const {
      return (+*this).round(x,rounding_type);
    }

    //! Fill the instance image with random values between specified range.
    CImg<T>& rand(const T val_min, const T val_max) {
      const float delta = (float)val_max - (float)val_min;
      cimg_for(*this,ptr,T) *ptr = (T)(val_min + cimg::rand()*delta);
      return *this;
    }

    CImg<T> get_rand(const T val_min, const T val_max) const {
      return (+*this).rand(val_min,val_max);
    }

    //@}
    //-----------------------------------
    //
    //! \name Usual Image Transformations
    //@{
    //-----------------------------------

    //! Fill an image by a value \p val.
    /**
       \param val = fill value
       \note All pixel values of the instance image will be initialized by \p val.
    **/
    CImg<T>& fill(const T val) {
      if (is_empty()) return *this;
      if (val && sizeof(T)!=1) cimg_for(*this,ptr,T) *ptr = val;
      else cimg_std::memset(data,(int)val,size()*sizeof(T));
      return *this;
    }

    CImg<T> get_fill(const T val) const {
      return CImg<T>(width,height,depth,dim).fill(val);
    }

    //! Fill sequentially all pixel values with values \a val0 and \a val1 respectively.
    CImg<T>& fill(const T val0, const T val1) {
      if (is_empty()) return *this;
      T *ptr, *ptr_end = end()-1;
      for (ptr = data; ptr<ptr_end; ) { *(ptr++) = val0; *(ptr++) = val1; }
      if (ptr!=ptr_end+1) *(ptr++) = val0;
      return *this;
    }

    CImg<T> get_fill(const T val0, const T val1) const {
      return CImg<T>(width,height,depth,dim).fill(val0,val1);
    }

    //! Fill sequentially all pixel values with values \a val0 and \a val1 and \a val2.
    CImg<T>& fill(const T val0, const T val1, const T val2) {
      if (is_empty()) return *this;
      T *ptr, *ptr_end = end()-2;
      for (ptr = data; ptr<ptr_end; ) { *(ptr++) = val0; *(ptr++) = val1; *(ptr++) = val2; }
      ptr_end+=2;
      switch (ptr_end-ptr) {
      case 2 : *(--ptr_end) = val1;
      case 1 : *(--ptr_end) = val0;
      }
      return *this;
    }

    CImg<T> get_fill(const T val0, const T val1, const T val2) const {
      return CImg<T>(width,height,depth,dim).fill(val0,val1,val2);
    }

    //! Fill sequentially all pixel values with values \a val0 and \a val1 and \a val2 and \a val3.
    CImg<T>& fill(const T val0, const T val1, const T val2, const T val3) {
      if (is_empty()) return *this;
      T *ptr, *ptr_end = end()-3;
      for (ptr = data; ptr<ptr_end; ) { *(ptr++) = val0; *(ptr++) = val1; *(ptr++) = val2; *(ptr++) = val3; }
      ptr_end+=3;
      switch (ptr_end-ptr) {
      case 3 : *(--ptr_end) = val2;
      case 2 : *(--ptr_end) = val1;
      case 1 : *(--ptr_end) = val0;
      }
      return *this;
    }

    CImg<T> get_fill(const T val0, const T val1, const T val2, const T val3) const {
      return CImg<T>(width,height,depth,dim).fill(val0,val1,val2,val3);
    }

    //! Fill sequentially all pixel values with values \a val0 and \a val1 and \a val2 and \a val3 and \a val4.
    CImg<T>& fill(const T val0, const T val1, const T val2, const T val3, const T val4) {
      if (is_empty()) return *this;
      T *ptr, *ptr_end = end()-4;
      for (ptr = data; ptr<ptr_end; ) { *(ptr++) = val0; *(ptr++) = val1; *(ptr++) = val2; *(ptr++) = val3; *(ptr++) = val4; }
      ptr_end+=4;
      switch (ptr_end-ptr) {
      case 4 : *(--ptr_end) = val3;
      case 3 : *(--ptr_end) = val2;
      case 2 : *(--ptr_end) = val1;
      case 1 : *(--ptr_end) = val0;
      }
      return *this;
    }

    CImg<T> get_fill(const T val0, const T val1, const T val2, const T val3, const T val4) const {
      return CImg<T>(width,height,depth,dim).fill(val0,val1,val2,val3,val4);
    }

    //! Fill sequentially all pixel values with values \a val0 and \a val1 and \a val2 and \a val3 and \a val4 and \a val5.
    CImg<T>& fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5) {
      if (is_empty()) return *this;
      T *ptr, *ptr_end = end()-5;
      for (ptr = data; ptr<ptr_end; ) {
        *(ptr++) = val0; *(ptr++) = val1; *(ptr++) = val2; *(ptr++) = val3; *(ptr++) = val4; *(ptr++) = val5;
      }
      ptr_end+=5;
      switch (ptr_end-ptr) {
      case 5 : *(--ptr_end) = val4;
      case 4 : *(--ptr_end) = val3;
      case 3 : *(--ptr_end) = val2;
      case 2 : *(--ptr_end) = val1;
      case 1 : *(--ptr_end) = val0;
      }
      return *this;
    }

    CImg<T> get_fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5) const {
      return CImg<T>(width,height,depth,dim).fill(val0,val1,val2,val3,val4,val5);
    }

    //! Fill sequentially pixel values.
    CImg<T>& fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5, const T val6) {
      if (is_empty()) return *this;
      T *ptr, *ptr_end = end()-6;
      for (ptr = data; ptr<ptr_end; ) {
        *(ptr++) = val0; *(ptr++) = val1; *(ptr++) = val2; *(ptr++) = val3; *(ptr++) = val4; *(ptr++) = val5; *(ptr++) = val6;
      }
      ptr_end+=6;
      switch (ptr_end-ptr) {
      case 6 : *(--ptr_end) = val5;
      case 5 : *(--ptr_end) = val4;
      case 4 : *(--ptr_end) = val3;
      case 3 : *(--ptr_end) = val2;
      case 2 : *(--ptr_end) = val1;
      case 1 : *(--ptr_end) = val0;
      }
      return *this;
    }

    CImg<T> get_fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5, const T val6) const {
      return CImg<T>(width,height,depth,dim).fill(val0,val1,val2,val3,val4,val5,val6);
    }

    //! Fill sequentially pixel values.
    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) {
      if (is_empty()) return *this;
      T *ptr, *ptr_end = end()-7;
      for (ptr = data; ptr<ptr_end; ) {
        *(ptr++) = val0; *(ptr++) = val1; *(ptr++) = val2; *(ptr++) = val3;
        *(ptr++) = val4; *(ptr++) = val5; *(ptr++) = val6; *(ptr++) = val7;
      }
      ptr_end+=7;
      switch (ptr_end-ptr) {
      case 7 : *(--ptr_end) = val6;
      case 6 : *(--ptr_end) = val5;
      case 5 : *(--ptr_end) = val4;
      case 4 : *(--ptr_end) = val3;
      case 3 : *(--ptr_end) = val2;
      case 2 : *(--ptr_end) = val1;
      case 1 : *(--ptr_end) = val0;
      }
      return *this;
    }

    CImg<T> get_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 {
      return CImg<T>(width,height,depth,dim).fill(val0,val1,val2,val3,val4,val5,val6,val7);
    }

    //! Fill sequentially pixel values.
    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) {
      if (is_empty()) return *this;
      T *ptr, *ptr_end = end()-8;
      for (ptr = data; ptr<ptr_end; ) {
        *(ptr++) = val0; *(ptr++) = val1; *(ptr++) = val2;
        *(ptr++) = val3; *(ptr++) = val4; *(ptr++) = val5;
        *(ptr++) = val6; *(ptr++) = val7; *(ptr++) = val8;
      }
      ptr_end+=8;
      switch (ptr_end-ptr) {
      case 8 : *(--ptr_end) = val7;
      case 7 : *(--ptr_end) = val6;
      case 6 : *(--ptr_end) = val5;
      case 5 : *(--ptr_end) = val4;
      case 4 : *(--ptr_end) = val3;
      case 3 : *(--ptr_end) = val2;
      case 2 : *(--ptr_end) = val1;
      case 1 : *(--ptr_end) = val0;
      }
      return *this;
    }

    CImg<T> get_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 {
      return CImg<T>(width,height,depth,dim).fill(val0,val1,val2,val3,val4,val5,val6,val7,val8);
    }

    //! Fill sequentially pixel values.
    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) {
      if (is_empty()) return *this;
      T *ptr, *ptr_end = end()-9;
      for (ptr = data; ptr<ptr_end; ) {
        *(ptr++) = val0; *(ptr++) = val1; *(ptr++) = val2; *(ptr++) = val3; *(ptr++) = val4;
        *(ptr++) = val5; *(ptr++) = val6; *(ptr++) = val7; *(ptr++) = val8; *(ptr++) = val9;
      }
      ptr_end+=9;
      switch (ptr_end-ptr) {
      case 9 : *(--ptr_end) = val8;
      case 8 : *(--ptr_end) = val7;
      case 7 : *(--ptr_end) = val6;
      case 6 : *(--ptr_end) = val5;
      case 5 : *(--ptr_end) = val4;
      case 4 : *(--ptr_end) = val3;
      case 3 : *(--ptr_end) = val2;
      case 2 : *(--ptr_end) = val1;
      case 1 : *(--ptr_end) = val0;
      }
      return *this;
    }

    CImg<T> get_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 {
      return CImg<T>(width,height,depth,dim).fill(val0,val1,val2,val3,val4,val5,val6,val7,val8,val9);
    }

    //! Fill sequentially pixel values.
    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) {
      if (is_empty()) return *this;
      T *ptr, *ptr_end = end()-10;
      for (ptr = data; ptr<ptr_end; ) {
        *(ptr++) = val0; *(ptr++) = val1; *(ptr++) = val2; *(ptr++) = val3; *(ptr++) = val4;
        *(ptr++) = val5; *(ptr++) = val6; *(ptr++) = val7; *(ptr++) = val8; *(ptr++) = val9;
        *(ptr++) = val10;
      }
      ptr_end+=10;
      switch (ptr_end-ptr) {
      case 10 : *(--ptr_end) = val9;
      case 9 : *(--ptr_end) = val8;
      case 8 : *(--ptr_end) = val7;
      case 7 : *(--ptr_end) = val6;
      case 6 : *(--ptr_end) = val5;
      case 5 : *(--ptr_end) = val4;
      case 4 : *(--ptr_end) = val3;
      case 3 : *(--ptr_end) = val2;
      case 2 : *(--ptr_end) = val1;
      case 1 : *(--ptr_end) = val0;
      }
      return *this;
    }

    CImg<T> get_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 {
      return CImg<T>(width,height,depth,dim).fill(val0,val1,val2,val3,val4,val5,val6,val7,val8,val9,val10);
    }

    //! Fill sequentially pixel values.
    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 *ptr, *ptr_end = end()-11;
      for (ptr = data; ptr<ptr_end; ) {
        *(ptr++) = val0; *(ptr++) = val1; *(ptr++) = val2; *(ptr++) = val3; *(ptr++) = val4; *(ptr++) = val5;
        *(ptr++) = val6; *(ptr++) = val7; *(ptr++) = val8; *(ptr++) = val9; *(ptr++) = val10; *(ptr++) = val11;
      }
      ptr_end+=11;
      switch (ptr_end-ptr) {
      case 11 : *(--ptr_end) = val10;
      case 10 : *(--ptr_end) = val9;
      case 9 : *(--ptr_end) = val8;
      case 8 : *(--ptr_end) = val7;
      case 7 : *(--ptr_end) = val6;
      case 6 : *(--ptr_end) = val5;
      case 5 : *(--ptr_end) = val4;
      case 4 : *(--ptr_end) = val3;
      case 3 : *(--ptr_end) = val2;
      case 2 : *(--ptr_end) = val1;
      case 1 : *(--ptr_end) = val0;
      }
      return *this;
    }

    CImg<T> get_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) const {
      return CImg<T>(width,height,depth,dim).fill(val0,val1,val2,val3,val4,val5,val6,val7,val8,val9,val10,val11);
    }

    //! Fill sequentially pixel values.
    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, const T val12) {
      if (is_empty()) return *this;
      T *ptr, *ptr_end = end()-12;
      for (ptr = data; ptr<ptr_end; ) {
        *(ptr++) = val0; *(ptr++) = val1; *(ptr++) = val2; *(ptr++) = val3; *(ptr++) = val4; *(ptr++) = val5;
        *(ptr++) = val6; *(ptr++) = val7; *(ptr++) = val8; *(ptr++) = val9; *(ptr++) = val10; *(ptr++) = val11;
        *(ptr++) = val12;
      }
      ptr_end+=12;
      switch (ptr_end-ptr) {
      case 12 : *(--ptr_end) = val11;
      case 11 : *(--ptr_end) = val10;
      case 10 : *(--ptr_end) = val9;
      case 9 : *(--ptr_end) = val8;
      case 8 : *(--ptr_end) = val7;
      case 7 : *(--ptr_end) = val6;
      case 6 : *(--ptr_end) = val5;
      case 5 : *(--ptr_end) = val4;
      case 4 : *(--ptr_end) = val3;
      case 3 : *(--ptr_end) = val2;
      case 2 : *(--ptr_end) = val1;
      case 1 : *(--ptr_end) = val0;
      }
      return *this;
    }

    CImg<T> get_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, const T val12) const {
      return CImg<T>(width,height,depth,dim).fill(val0,val1,val2,val3,val4,val5,val6,val7,val8,val9,val10,val11,val12);
    }

    //! Fill sequentially pixel values.
    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, const T val12,
                  const T val13) {
      if (is_empty()) return *this;
      T *ptr, *ptr_end = end()-13;
      for (ptr = data; ptr<ptr_end; ) {
        *(ptr++) = val0; *(ptr++) = val1; *(ptr++) = val2; *(ptr++) = val3; *(ptr++) = val4; *(ptr++) = val5;
        *(ptr++) = val6; *(ptr++) = val7; *(ptr++) = val8; *(ptr++) = val9; *(ptr++) = val10; *(ptr++) = val11;
        *(ptr++) = val12; *(ptr++) = val13;
      }
      ptr_end+=13;
      switch (ptr_end-ptr) {
      case 13 : *(--ptr_end) = val12;
      case 12 : *(--ptr_end) = val11;
      case 11 : *(--ptr_end) = val10;
      case 10 : *(--ptr_end) = val9;
      case 9 : *(--ptr_end) = val8;
      case 8 : *(--ptr_end) = val7;
      case 7 : *(--ptr_end) = val6;
      case 6 : *(--ptr_end) = val5;
      case 5 : *(--ptr_end) = val4;
      case 4 : *(--ptr_end) = val3;
      case 3 : *(--ptr_end) = val2;
      case 2 : *(--ptr_end) = val1;
      case 1 : *(--ptr_end) = val0;
      }
      return *this;
    }

    CImg<T> get_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, const T val12,
                     const T val13) const {
      return CImg<T>(width,height,depth,dim).fill(val0,val1,val2,val3,val4,val5,val6,val7,val8,val9,val10,val11,val12,
                                                  val13);
    }

    //! Fill sequentially pixel values.
    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, const T val12,
                  const T val13, const T val14) {
      if (is_empty()) return *this;
      T *ptr, *ptr_end = end()-14;
      for (ptr = data; ptr<ptr_end; ) {
        *(ptr++) = val0; *(ptr++) = val1; *(ptr++) = val2; *(ptr++) = val3; *(ptr++) = val4; *(ptr++) = val5;
        *(ptr++) = val6; *(ptr++) = val7; *(ptr++) = val8; *(ptr++) = val9; *(ptr++) = val10; *(ptr++) = val11;
        *(ptr++) = val12; *(ptr++) = val13; *(ptr++) = val14;
      }
      ptr_end+=14;
      switch (ptr_end-ptr) {
      case 14 : *(--ptr_end) = val13;
      case 13 : *(--ptr_end) = val12;
      case 12 : *(--ptr_end) = val11;
      case 11 : *(--ptr_end) = val10;
      case 10 : *(--ptr_end) = val9;
      case 9 : *(--ptr_end) = val8;
      case 8 : *(--ptr_end) = val7;
      case 7 : *(--ptr_end) = val6;
      case 6 : *(--ptr_end) = val5;
      case 5 : *(--ptr_end) = val4;
      case 4 : *(--ptr_end) = val3;
      case 3 : *(--ptr_end) = val2;
      case 2 : *(--ptr_end) = val1;
      case 1 : *(--ptr_end) = val0;
      }
      return *this;
    }

    CImg<T> get_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, const T val12,
                     const T val13, const T val14) const {
      return CImg<T>(width,height,depth,dim).fill(val0,val1,val2,val3,val4,val5,val6,val7,val8,val9,val10,val11,val12,
                                                  val13,val14);
    }

    //! Fill sequentially pixel values.
    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, const T val12,
                  const T val13, const T val14, const T val15) {
      if (is_empty()) return *this;
      T *ptr, *ptr_end = end()-15;
      for (ptr = data; ptr<ptr_end; ) {
        *(ptr++) = val0; *(ptr++) = val1; *(ptr++) = val2; *(ptr++) = val3; *(ptr++) = val4; *(ptr++) = val5;
        *(ptr++) = val6; *(ptr++) = val7; *(ptr++) = val8; *(ptr++) = val9; *(ptr++) = val10; *(ptr++) = val11;
        *(ptr++) = val12; *(ptr++) = val13; *(ptr++) = val14; *(ptr++) = val15;
      }
      ptr_end+=15;
      switch (ptr_end-ptr) {
      case 15 : *(--ptr_end) = val14;
      case 14 : *(--ptr_end) = val13;
      case 13 : *(--ptr_end) = val12;
      case 12 : *(--ptr_end) = val11;
      case 11 : *(--ptr_end) = val10;
      case 10 : *(--ptr_end) = val9;
      case 9 : *(--ptr_end) = val8;
      case 8 : *(--ptr_end) = val7;
      case 7 : *(--ptr_end) = val6;
      case 6 : *(--ptr_end) = val5;
      case 5 : *(--ptr_end) = val4;
      case 4 : *(--ptr_end) = val3;
      case 3 : *(--ptr_end) = val2;
      case 2 : *(--ptr_end) = val1;
      case 1 : *(--ptr_end) = val0;
      }
      return *this;
    }

    CImg<T> get_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, const T val12,
                     const T val13, const T val14, const T val15) const {
      return CImg<T>(width,height,depth,dim).fill(val0,val1,val2,val3,val4,val5,val6,val7,val8,val9,val10,val11,val12,
                                                  val13,val14,val15);
    }

    //! Fill image values according to the values found in the specified string.
    CImg<T>& fill(const char *const values, const bool repeat_pattern) {
      if (is_empty() || !values) return *this;
      T *ptrd = data, *ptr_end = data + size();
      const char *nvalues = values;
      const unsigned int siz = size();
      char cval[64] = { 0 },  sep = 0;
      int err = 0; double val = 0; unsigned int nb = 0;
      while ((err=cimg_std::sscanf(nvalues,"%63[ \n\t0-9e.+-]%c",cval,&sep))>0 &&
             cimg_std::sscanf(cval,"%lf",&val)>0 && nb<siz) {
        nvalues += cimg::strlen(cval);
        *(ptrd++) = (T)val;
        ++nb;
        if (err!=2) break; else ++nvalues;
      }
      if (repeat_pattern && nb) for (T *ptrs = data; ptrd<ptr_end; ++ptrs) *(ptrd++) = *ptrs;
      return *this;
    }

    CImg<T> get_fill(const char *const values, const bool repeat_pattern) const {
      return repeat_pattern?CImg<T>(width,height,depth,dim).fill(values,repeat_pattern):(+*this).fill(values,repeat_pattern);
    }

    //! Fill image values according to the values found in the specified image.
    template<typename t>
    CImg<T>& fill(const CImg<t>& values, const bool repeat_pattern=true) {
      if (is_empty() || !values) return *this;
      T *ptrd = data, *ptrd_end = ptrd + size();
      for (t *ptrs = values.data, *ptrs_end = ptrs + values.size(); ptrs<ptrs_end && ptrd<ptrd_end; ++ptrs) *(ptrd++) = (T)*ptrs;
      if (repeat_pattern && ptrd<ptrd_end) for (T *ptrs = data; ptrd<ptrd_end; ++ptrs) *(ptrd++) = *ptrs;
      return *this;
    }

    template<typename t>
    CImg<T> get_fill(const CImg<t>& values, const bool repeat_pattern=true) const {
      return repeat_pattern?CImg<T>(width,height,depth,dim).fill(values,repeat_pattern):(+*this).fill(values,repeat_pattern);
    }

    //! Fill image values along the X-axis at the specified pixel position (y,z,v).
    CImg<T>& fillX(const unsigned int y, const unsigned int z, const unsigned int v, const int a0, ...) {
#define _cimg_fill1(x,y,z,v,off,siz,t) { \
    va_list ap; va_start(ap,a0); T *ptrd = ptr(x,y,z,v); *ptrd = (T)a0; \
    for (unsigned int k = 1; k<siz; ++k) { ptrd+=off; *ptrd = (T)va_arg(ap,t); } \
    va_end(ap); }
      if (y<height && z<depth && v<dim) _cimg_fill1(0,y,z,v,1,width,int);
      return *this;
    }

    CImg<T>& fillX(const unsigned int y, const unsigned int z, const unsigned int v, const double a0, ...) {
      if (y<height && z<depth && v<dim) _cimg_fill1(0,y,z,v,1,width,double);
      return *this;
    }

    //! Fill image values along the Y-axis at the specified pixel position (x,z,v).
    CImg<T>& fillY(const unsigned int x, const unsigned int z, const unsigned int v, const int a0, ...) {
      if (x<width && z<depth && v<dim) _cimg_fill1(x,0,z,v,width,height,int);
      return *this;
    }

    CImg<T>& fillY(const unsigned int x, const unsigned int z, const unsigned int v, const double a0, ...) {
      if (x<width && z<depth && v<dim) _cimg_fill1(x,0,z,v,width,height,double);
      return *this;
    }

    //! Fill image values along the Z-axis at the specified pixel position (x,y,v).
    CImg<T>& fillZ(const unsigned int x, const unsigned int y, const unsigned int v, const int a0, ...) {
      const unsigned int wh = width*height;
      if (x<width && y<height && v<dim) _cimg_fill1(x,y,0,v,wh,depth,int);
      return *this;
    }

    CImg<T>& fillZ(const unsigned int x, const unsigned int y, const unsigned int v, const double a0, ...) {
      const unsigned int wh = width*height;
      if (x<width && y<height && v<dim) _cimg_fill1(x,y,0,v,wh,depth,double);
      return *this;
    }

    //! Fill image values along the V-axis at the specified pixel position (x,y,z).
    CImg<T>& fillV(const unsigned int x, const unsigned int y, const unsigned int z, const int a0, ...) {
      const unsigned int whz = width*height*depth;
      if (x<width && y<height && z<depth) _cimg_fill1(x,y,z,0,whz,dim,int);
      return *this;
    }

    CImg<T>& fillV(const unsigned int x, const unsigned int y, const unsigned int z, const double a0, ...) {
      const unsigned int whz = width*height*depth;
      if (x<width && y<height && z<depth) _cimg_fill1(x,y,z,0,whz,dim,double);
      return *this;
    }

    //! Linear normalization of the pixel values between \a a and \a b.
    CImg<T>& normalize(const T a, const T b) {
      if (is_empty()) return *this;
      const T na = a<b?a:b, nb = a<b?b:a;
      T m, M = maxmin(m);
      const Tfloat fm = (Tfloat)m, fM = (Tfloat)M;
      if (m==M) return fill(0);
      if (m!=na || M!=nb) cimg_for(*this,ptr,T) *ptr = (T)((*ptr-fm)/(fM-fm)*(nb-na)+na);
      return *this;
    }

    CImg<T> get_normalize(const T a, const T b) const {
      return (+*this).normalize(a,b);
    }

    //! Cut pixel values between \a a and \a b.
    CImg<T>& cut(const T a, const T b) {
      if (is_empty()) return *this;
      const T na = a<b?a:b, nb = a<b?b:a;
      cimg_for(*this,ptr,T) *ptr = (*ptr<na)?na:((*ptr>nb)?nb:*ptr);
      return *this;
    }

    CImg<T> get_cut(const T a, const T b) const {
      return (+*this).cut(a,b);
    }

    //! Quantize pixel values into \n levels.
    CImg<T>& quantize(const unsigned int n, const bool keep_range=true) {
      if (is_empty()) return *this;
      if (!n)
        throw CImgArgumentException("CImg<%s>::quantize() : Cannot quantize image to 0 values.",
                                    pixel_type());
      Tfloat m, M = (Tfloat)maxmin(m), range = M - m;
      if (range>0) {
        if (keep_range) cimg_for(*this,ptr,T) {
          const unsigned int val = (unsigned int)((*ptr-m)*n/range);
          *ptr = (T)(m + cimg::min(val,n-1)*range/n);
        } else cimg_for(*this,ptr,T) {
          const unsigned int val = (unsigned int)((*ptr-m)*n/range);
          *ptr = (T)cimg::min(val,n-1);
        }
      }
      return *this;
    }

    CImg<T> get_quantize(const unsigned int n, const bool keep_range=true) const {
      return (+*this).quantize(n,keep_range);
    }

    //! Threshold the image.
    /**
       \param value Threshold value.
       \param soft Enable soft thresholding.
       \param strict Tells if the threshold is strict.
    **/
    CImg<T>& threshold(const T value, const bool soft=false, const bool strict=false) {
      if (is_empty()) return *this;
      if (strict) {
        if (soft) cimg_for(*this,ptr,T) { const T v = *ptr; *ptr = v>value?(T)(v-value):v<-value?(T)(v+value):(T)0; }
        else cimg_for(*this,ptr,T) *ptr = *ptr>value?(T)1:(T)0;
      } else {
        if (soft) cimg_for(*this,ptr,T) { const T v = *ptr; *ptr = v>=value?(T)(v-value):v<=-value?(T)(v+value):(T)0; }
        else cimg_for(*this,ptr,T) *ptr = *ptr>=value?(T)1:(T)0;
      }
      return *this;
    }

    CImg<T> get_threshold(const T value, const bool soft=false, const bool strict=false) const {
      return (+*this).threshold(value,soft,strict);
    }

    //! Rotate an image.
    /**
       \param angle = rotation angle (in degrees).
       \param cond = rotation type. can be :
       - 0 = zero-value at borders
       - 1 = nearest pixel.
       - 2 = Fourier style.
       \note Returned image will probably have a different size than the instance image *this.
    **/
    CImg<T>& rotate(const float angle, const unsigned int border_conditions=3, const unsigned int interpolation=1) {
      return get_rotate(angle,border_conditions,interpolation).transfer_to(*this);
    }

    CImg<T> get_rotate(const float angle, const unsigned int border_conditions=3, const unsigned int interpolation=1) const {
      if (is_empty()) return *this;
      CImg<T> dest;
      const float nangle = cimg::mod(angle,360.0f);
      if (border_conditions!=1 && cimg::mod(nangle,90.0f)==0) { // optimized version for orthogonal angles
        const int wm1 = dimx()-1, hm1 = dimy()-1;
        const int iangle = (int)nangle/90;
        switch (iangle) {
        case 1 : {
          dest.assign(height,width,depth,dim);
          cimg_forXYZV(dest,x,y,z,v) dest(x,y,z,v) = (*this)(y,hm1-x,z,v);
        } break;
        case 2 : {
          dest.assign(width,height,depth,dim);
          cimg_forXYZV(dest,x,y,z,v) dest(x,y,z,v) = (*this)(wm1-x,hm1-y,z,v);
        } break;
        case 3 : {
          dest.assign(height,width,depth,dim);
          cimg_forXYZV(dest,x,y,z,v) dest(x,y,z,v) = (*this)(wm1-y,x,z,v);
        } break;
        default :
          return *this;
        }
      } else { // generic version
        const float
          rad = (float)(nangle*cimg::valuePI/180.0),
          ca = (float)cimg_std::cos(rad),
          sa = (float)cimg_std::sin(rad),
          ux = cimg::abs(width*ca), uy = cimg::abs(width*sa),
          vx = cimg::abs(height*sa), vy = cimg::abs(height*ca),
          w2 = 0.5f*width, h2 = 0.5f*height,
          dw2 = 0.5f*(ux+vx), dh2 = 0.5f*(uy+vy);
        dest.assign((int)(ux+vx), (int)(uy+vy),depth,dim);
        switch (border_conditions) {
        case 0 : {
          switch (interpolation) {
          case 2 : {
            cimg_forXY(dest,x,y) cimg_forZV(*this,z,v)
              dest(x,y,z,v) = (T)cubic_atXY(w2 + (x-dw2)*ca + (y-dh2)*sa,h2 - (x-dw2)*sa + (y-dh2)*ca,z,v,0);
          } break;
          case 1 : {
            cimg_forXY(dest,x,y) cimg_forZV(*this,z,v)
              dest(x,y,z,v) = (T)linear_atXY(w2 + (x-dw2)*ca + (y-dh2)*sa,h2 - (x-dw2)*sa + (y-dh2)*ca,z,v,0);
          } break;
          default : {
            cimg_forXY(dest,x,y) cimg_forZV(*this,z,v)
              dest(x,y,z,v) = atXY((int)(w2 + (x-dw2)*ca + (y-dh2)*sa),(int)(h2 - (x-dw2)*sa + (y-dh2)*ca),z,v,0);
          }
          }
        } break;
        case 1 : {
          switch (interpolation) {
          case 2 :
            cimg_forXY(dest,x,y) cimg_forZV(*this,z,v)
              dest(x,y,z,v) = (T)cubic_atXY(w2 + (x-dw2)*ca + (y-dh2)*sa,h2 - (x-dw2)*sa + (y-dh2)*ca,z,v);
            break;
          case 1 :
            cimg_forXY(dest,x,y) cimg_forZV(*this,z,v)
              dest(x,y,z,v) = (T)linear_atXY(w2 + (x-dw2)*ca + (y-dh2)*sa,h2 - (x-dw2)*sa + (y-dh2)*ca,z,v);
            break;
          default :
            cimg_forXY(dest,x,y) cimg_forZV(*this,z,v)
              dest(x,y,z,v) = atXY((int)(w2 + (x-dw2)*ca + (y-dh2)*sa),(int)(h2 - (x-dw2)*sa + (y-dh2)*ca),z,v);
          }
        } break;
        case 2 : {
          switch (interpolation) {
          case 2 :
            cimg_forXY(dest,x,y) cimg_forZV(*this,z,v)
              dest(x,y,z,v) = (T)cubic_atXY(cimg::mod(w2 + (x-dw2)*ca + (y-dh2)*sa,(float)dimx()),
                                            cimg::mod(h2 - (x-dw2)*sa + (y-dh2)*ca,(float)dimy()),z,v);
            break;
          case 1 :
            cimg_forXY(dest,x,y) cimg_forZV(*this,z,v)
              dest(x,y,z,v) = (T)linear_atXY(cimg::mod(w2 + (x-dw2)*ca + (y-dh2)*sa,(float)dimx()),
                                             cimg::mod(h2 - (x-dw2)*sa + (y-dh2)*ca,(float)dimy()),z,v);
            break;
          default :
            cimg_forXY(dest,x,y) cimg_forZV(*this,z,v)
              dest(x,y,z,v) = (*this)(cimg::mod((int)(w2 + (x-dw2)*ca + (y-dh2)*sa),dimx()),
                                      cimg::mod((int)(h2 - (x-dw2)*sa + (y-dh2)*ca),dimy()),z,v);
          }
        } break;
        default :
          throw CImgArgumentException("CImg<%s>::get_rotate() : Invalid border conditions %d (should be 0,1 or 2).",
                                      pixel_type(),border_conditions);
        }
      }
      return dest;
    }

    //! Rotate an image around a center point (\c cx,\c cy).
    /**
       \param angle = rotation angle (in degrees).
       \param cx = X-coordinate of the rotation center.
       \param cy = Y-coordinate of the rotation center.
       \param zoom = zoom.
       \param cond = rotation type. can be :
       - 0 = zero-value at borders
       - 1 = repeat image at borders
       - 2 = zero-value at borders and linear interpolation
    **/
    CImg<T>& rotate(const float angle, const float cx, const float cy, const float zoom,
                    const unsigned int border_conditions=3, const unsigned int interpolation=1) {
      return get_rotate(angle,cx,cy,zoom,border_conditions,interpolation).transfer_to(*this);
    }

    CImg<T> get_rotate(const float angle, const float cx, const float cy, const float zoom,
                       const unsigned int border_conditions=3, const unsigned int interpolation=1) const {
      if (interpolation>2)
        throw CImgArgumentException("CImg<%s>::get_rotate() : Invalid interpolation parameter %d (should be {0=none, 1=linear or 2=cubic}).",
                                    pixel_type(),interpolation);
      if (is_empty()) return *this;
      CImg<T> dest(width,height,depth,dim);
      const float nangle = cimg::mod(angle,360.0f);
      if (border_conditions!=1 && zoom==1 && cimg::mod(nangle,90.0f)==0) { // optimized version for orthogonal angles
        const int iangle = (int)nangle/90;
        switch (iangle) {
        case 1 : {
          dest.fill(0);
          const unsigned int
            xmin = cimg::max(0,(dimx()-dimy())/2), xmax = cimg::min(width,xmin+height),
            ymin = cimg::max(0,(dimy()-dimx())/2), ymax = cimg::min(height,ymin+width),
            xoff = xmin + cimg::min(0,(dimx()-dimy())/2),
            yoff = ymin + cimg::min(0,(dimy()-dimx())/2);
          cimg_forZV(dest,z,v) for (unsigned int y = ymin; y<ymax; ++y) for (unsigned int x = xmin; x<xmax; ++x)
            dest(x,y,z,v) = (*this)(y-yoff,height-1-x+xoff,z,v);
        } break;
        case 2 : {
          cimg_forXYZV(dest,x,y,z,v) dest(x,y,z,v) = (*this)(width-1-x,height-1-y,z,v);
        } break;
        case 3 : {
          dest.fill(0);
          const unsigned int
            xmin = cimg::max(0,(dimx()-dimy())/2), xmax = cimg::min(width,xmin+height),
            ymin = cimg::max(0,(dimy()-dimx())/2), ymax = cimg::min(height,ymin+width),
            xoff = xmin + cimg::min(0,(dimx()-dimy())/2),
            yoff = ymin + cimg::min(0,(dimy()-dimx())/2);
          cimg_forZV(dest,z,v) for (unsigned int y = ymin; y<ymax; ++y) for (unsigned int x = xmin; x<xmax; ++x)
            dest(x,y,z,v) = (*this)(width-1-y+yoff,x-xoff,z,v);
        } break;
        default :
          return *this;
        }
      } else {
        const float
          rad = (float)((nangle*cimg::valuePI)/180.0),
          ca = (float)cimg_std::cos(rad)/zoom,
          sa = (float)cimg_std::sin(rad)/zoom;
        switch (border_conditions) { // generic version
        case 0 : {
          switch (interpolation) {
          case 2 : {
            cimg_forXY(dest,x,y)
              cimg_forZV(*this,z,v)
              dest(x,y,z,v) = (T)cubic_atXY(cx + (x-cx)*ca + (y-cy)*sa,cy - (x-cx)*sa + (y-cy)*ca,z,v,0);
          } break;
          case 1 : {
            cimg_forXY(dest,x,y)
              cimg_forZV(*this,z,v)
              dest(x,y,z,v) = (T)linear_atXY(cx + (x-cx)*ca + (y-cy)*sa,cy - (x-cx)*sa + (y-cy)*ca,z,v,0);
          } break;
          default : {
            cimg_forXY(dest,x,y)
              cimg_forZV(*this,z,v)
              dest(x,y,z,v) = atXY((int)(cx + (x-cx)*ca + (y-cy)*sa),(int)(cy - (x-cx)*sa + (y-cy)*ca),z,v,0);
          }
          }
        } break;
        case 1 : {
          switch (interpolation) {
          case 2 : {
            cimg_forXY(dest,x,y)
              cimg_forZV(*this,z,v)
              dest(x,y,z,v) = (T)cubic_atXY(cx + (x-cx)*ca + (y-cy)*sa,cy - (x-cx)*sa + (y-cy)*ca,z,v);
            } break;
          case 1 : {
            cimg_forXY(dest,x,y)
              cimg_forZV(*this,z,v)
              dest(x,y,z,v) = (T)linear_atXY(cx + (x-cx)*ca + (y-cy)*sa,cy - (x-cx)*sa + (y-cy)*ca,z,v);
          } break;
          default : {
            cimg_forXY(dest,x,y)
              cimg_forZV(*this,z,v)
              dest(x,y,z,v) = atXY((int)(cx + (x-cx)*ca + (y-cy)*sa),(int)(cy - (x-cx)*sa + (y-cy)*ca),z,v);
          }
          }
        } break;
        case 2 : {
          switch (interpolation) {
          case 2 : {
            cimg_forXY(dest,x,y)
              cimg_forZV(*this,z,v)
              dest(x,y,z,v) = (T)cubic_atXY(cimg::mod(cx + (x-cx)*ca + (y-cy)*sa,(float)dimx()),
                                            cimg::mod(cy - (x-cx)*sa + (y-cy)*ca,(float)dimy()),z,v);
            } break;
          case 1 : {
            cimg_forXY(dest,x,y)
              cimg_forZV(*this,z,v)
              dest(x,y,z,v) = (T)linear_atXY(cimg::mod(cx + (x-cx)*ca + (y-cy)*sa,(float)dimx()),
                                             cimg::mod(cy - (x-cx)*sa + (y-cy)*ca,(float)dimy()),z,v);
          } break;
          default : {
            cimg_forXY(dest,x,y)
              cimg_forZV(*this,z,v)
              dest(x,y,z,v) = (*this)(cimg::mod((int)(cx + (x-cx)*ca + (y-cy)*sa),dimx()),
                                      cimg::mod((int)(cy - (x-cx)*sa + (y-cy)*ca),dimy()),z,v);
          }
          }
        } break;
        default :
          throw CImgArgumentException("CImg<%s>::get_rotate() : Incorrect border conditions %d (should be 0,1 or 2).",
                                      pixel_type(),border_conditions);
        }
      }
      return dest;
    }

    //! Resize an image.
    /**
       \param pdx Number of columns (new size along the X-axis).
       \param pdy Number of rows (new size along the Y-axis).
       \param pdz Number of slices (new size along the Z-axis).
       \param pdv Number of vector-channels (new size along the V-axis).
       \param interpolation_type Method of interpolation :
       - -1 = no interpolation : raw memory resizing.
       - 0 = no interpolation : additional space is filled according to \p border_condition.
       - 1 = bloc interpolation (nearest point).
       - 2 = moving average interpolation.
       - 3 = linear interpolation.
       - 4 = grid interpolation.
       - 5 = bi-cubic interpolation.
       \param border_condition Border condition type.
       \param center Set centering type (only if \p interpolation_type=0).
       \note If pd[x,y,z,v]<0, it corresponds to a percentage of the original size (the default value is -100).
    **/
    CImg<T>& resize(const int pdx, const int pdy=-100, const int pdz=-100, const int pdv=-100,
                    const int interpolation_type=1, const int border_condition=-1, const bool center=false) {
      if (!pdx || !pdy || !pdz || !pdv) return assign();
      const unsigned int
        tdx = pdx<0?-pdx*width/100:pdx,
        tdy = pdy<0?-pdy*height/100:pdy,
        tdz = pdz<0?-pdz*depth/100:pdz,
        tdv = pdv<0?-pdv*dim/100:pdv,
        dx = tdx?tdx:1,
        dy = tdy?tdy:1,
        dz = tdz?tdz:1,
        dv = tdv?tdv:1;
      if (width==dx && height==dy && depth==dz && dim==dv) return *this;
      if (interpolation_type==-1 && dx*dy*dz*dv==size()) {
        width = dx; height = dy; depth = dz; dim = dv;
        return *this;
      }
      return get_resize(dx,dy,dz,dv,interpolation_type,border_condition,center).transfer_to(*this);
    }

    CImg<T> get_resize(const int pdx, const int pdy=-100, const int pdz=-100, const int pdv=-100,
                       const int interpolation_type=1, const int border_condition=-1, const bool center=false) const {
      if (!pdx || !pdy || !pdz || !pdv) return CImg<T>();
      const unsigned int
        tdx = pdx<0?-pdx*width/100:pdx,
        tdy = pdy<0?-pdy*height/100:pdy,
        tdz = pdz<0?-pdz*depth/100:pdz,
        tdv = pdv<0?-pdv*dim/100:pdv,
        dx = tdx?tdx:1,
        dy = tdy?tdy:1,
        dz = tdz?tdz:1,
        dv = tdv?tdv:1;
      if (width==dx && height==dy && depth==dz && dim==dv) return +*this;
      if (is_empty()) return CImg<T>(dx,dy,dz,dv,0);

      CImg<T> res;

      switch (interpolation_type) {
      case -1 : // Raw resizing
        cimg_std::memcpy(res.assign(dx,dy,dz,dv,0).data,data,sizeof(T)*cimg::min(size(),(long unsigned int)dx*dy*dz*dv));
        break;

      case 0 :  { // No interpolation
        const unsigned int bx = width-1, by = height-1, bz = depth-1, bv = dim-1;
        res.assign(dx,dy,dz,dv);
        switch (border_condition) {
        case 1 : {
          if (center) {
            const int
              x0 = (res.dimx()-dimx())/2,
              y0 = (res.dimy()-dimy())/2,
              z0 = (res.dimz()-dimz())/2,
              v0 = (res.dimv()-dimv())/2,
              x1 = x0 + (int)bx,
              y1 = y0 + (int)by,
              z1 = z0 + (int)bz,
              v1 = v0 + (int)bv;
            res.draw_image(x0,y0,z0,v0,*this);
            cimg_for_outXYZV(res,x0,y0,z0,v0,x1,y1,z1,v1,x,y,z,v) res(x,y,z,v) = _atXYZV(x-x0,y-y0,z-z0,v-v0);
          } else {
            res.draw_image(*this);
            cimg_for_outXYZV(res,0,0,0,0,bx,by,bz,bv,x,y,z,v) res(x,y,z,v) = _atXYZV(x,y,z,v);
          }
          } break;
        case 2 : {
          int nx0 = 0, ny0 = 0, nz0 = 0, nv0 = 0;
          if (center) {
            const int
              x0 = (res.dimx()-dimx())/2,
              y0 = (res.dimy()-dimy())/2,
              z0 = (res.dimz()-dimz())/2,
              v0 = (res.dimv()-dimv())/2;
            nx0 = x0>0?x0-(1+x0/width)*width:x0;
            ny0 = y0>0?y0-(1+y0/height)*height:y0;
            nz0 = z0>0?z0-(1+z0/depth)*depth:z0;
            nv0 = v0>0?v0-(1+v0/dim)*dim:v0;
          }
          for (int k = nv0; k<(int)dv; k+=dimv())
            for (int z = nz0; z<(int)dz; z+=dimz())
              for (int y = ny0; y<(int)dy; y+=dimy())
                for (int x = nx0; x<(int)dx; x+=dimx()) res.draw_image(x,y,z,k,*this);
          } break;
        default : {
          res.fill(0);
          if (center) res.draw_image((res.dimx()-dimx())/2,(res.dimy()-dimy())/2,(res.dimz()-dimz())/2,(res.dimv()-dimv())/2,*this);
          else res.draw_image(*this);
        }
        }
      } break;

      case 1 : { // Nearest-neighbor interpolation
        res.assign(dx,dy,dz,dv);
        unsigned int
          *const offx = new unsigned int[dx],
          *const offy = new unsigned int[dy+1],
          *const offz = new unsigned int[dz+1],
          *const offv = new unsigned int[dv+1],
          *poffx, *poffy, *poffz, *poffv,
          curr, old;
        const unsigned int wh = width*height, whd = width*height*depth, rwh = dx*dy, rwhd = dx*dy*dz;
        poffx = offx; curr = 0; { cimg_forX(res,x) { old=curr; curr=(x+1)*width/dx; *(poffx++) = (unsigned int)curr-(unsigned int)old; }}
        poffy = offy; curr = 0; { cimg_forY(res,y) { old=curr; curr=(y+1)*height/dy; *(poffy++) = width*((unsigned int)curr-(unsigned int)old); }} *poffy=0;
        poffz = offz; curr = 0; { cimg_forZ(res,z) { old=curr; curr=(z+1)*depth/dz; *(poffz++) = wh*((unsigned int)curr-(unsigned int)old); }} *poffz=0;
        poffv = offv; curr = 0; { cimg_forV(res,k) { old=curr; curr=(k+1)*dim/dv; *(poffv++) = whd*((unsigned int)curr-(unsigned int)old); }} *poffv=0;
        T *ptrd = res.data;
        const T* ptrv = data;
        poffv = offv;
        for (unsigned int k=0; k<dv; ) {
          const T *ptrz = ptrv;
          poffz = offz;
          for (unsigned int z=0; z<dz; ) {
            const T *ptry = ptrz;
            poffy = offy;
            for (unsigned int y=0; y<dy; ) {
              const T *ptrx = ptry;
              poffx = offx;
              cimg_forX(res,x) { *(ptrd++) = *ptrx; ptrx+=*(poffx++); }
              ++y;
              unsigned int dy = *(poffy++);
              for (;!dy && y<dy; cimg_std::memcpy(ptrd, ptrd-dx, sizeof(T)*dx), ++y, ptrd+=dx, dy=*(poffy++)) {}
              ptry+=dy;
            }
            ++z;
            unsigned int dz = *(poffz++);
            for (;!dz && z<dz; cimg_std::memcpy(ptrd, ptrd-rwh, sizeof(T)*rwh), ++z, ptrd+=rwh, dz=*(poffz++)) {}
            ptrz+=dz;
          }
          ++k;
          unsigned int dv = *(poffv++);
          for (;!dv && k<dv; cimg_std::memcpy(ptrd, ptrd-rwhd, sizeof(T)*rwhd), ++k, ptrd+=rwhd, dv=*(poffv++)) {}
          ptrv+=dv;
        }
        delete[] offx; delete[] offy; delete[] offz; delete[] offv;
      } break;

      case 2 : { // Moving average
        bool instance_first = true;
        if (dx!=width) {
          CImg<Tfloat> tmp(dx,height,depth,dim,0);
          for (unsigned int a = width*dx, b = width, c = dx, s = 0, t = 0; a; ) {
            const unsigned int d = cimg::min(b,c);
            a-=d; b-=d; c-=d;
            cimg_forYZV(tmp,y,z,v) tmp(t,y,z,v)+=(Tfloat)(*this)(s,y,z,v)*d;
            if (!b) { cimg_forYZV(tmp,y,z,v) tmp(t,y,z,v)/=width; ++t; b = width; }
            if (!c) { ++s; c = dx; }
          }
          tmp.transfer_to(res);
          instance_first = false;
        }
        if (dy!=height) {
          CImg<Tfloat> tmp(dx,dy,depth,dim,0);
          for (unsigned int a = height*dy, b = height, c = dy, s = 0, t = 0; a; ) {
            const unsigned int d = cimg::min(b,c);
            a-=d; b-=d; c-=d;
            if (instance_first) cimg_forXZV(tmp,x,z,v) tmp(x,t,z,v)+=(Tfloat)(*this)(x,s,z,v)*d;
            else cimg_forXZV(tmp,x,z,v) tmp(x,t,z,v)+=(Tfloat)res(x,s,z,v)*d;
            if (!b) { cimg_forXZV(tmp,x,z,v) tmp(x,t,z,v)/=height; ++t; b = height; }
            if (!c) { ++s; c = dy; }
          }
          tmp.transfer_to(res);
          instance_first = false;
        }
        if (dz!=depth) {
          CImg<Tfloat> tmp(dx,dy,dz,dim,0);
          for (unsigned int a = depth*dz, b = depth, c = dz, s = 0, t = 0; a; ) {
            const unsigned int d = cimg::min(b,c);
            a-=d; b-=d; c-=d;
            if (instance_first) cimg_forXYV(tmp,x,y,v) tmp(x,y,t,v)+=(Tfloat)(*this)(x,y,s,v)*d;
            else cimg_forXYV(tmp,x,y,v) tmp(x,y,t,v)+=(Tfloat)res(x,y,s,v)*d;
            if (!b) { cimg_forXYV(tmp,x,y,v) tmp(x,y,t,v)/=depth; ++t; b = depth; }
            if (!c) { ++s; c = dz; }
          }
          tmp.transfer_to(res);
          instance_first = false;
        }
        if (dv!=dim) {
          CImg<Tfloat> tmp(dx,dy,dz,dv,0);
          for (unsigned int a = dim*dv, b = dim, c = dv, s = 0, t = 0; a; ) {
            const unsigned int d = cimg::min(b,c);
            a-=d; b-=d; c-=d;
            if (instance_first) cimg_forXYZ(tmp,x,y,z) tmp(x,y,z,t)+=(Tfloat)(*this)(x,y,z,s)*d;
            else cimg_forXYZ(tmp,x,y,z) tmp(x,y,z,t)+=(Tfloat)res(x,y,z,s)*d;
            if (!b) { cimg_forXYZ(tmp,x,y,z) tmp(x,y,z,t)/=dim; ++t; b = dim; }
            if (!c) { ++s; c = dv; }
          }
          tmp.transfer_to(res);
          instance_first = false;
        }
      } break;

      case 3 : { // Linear interpolation
        const unsigned int dimmax = cimg::max(dx,dy,dz,dv);
        const float
          sx = (border_condition<0 && dx>width )?(dx>1?(width-1.0f)/(dx-1) :0):(float)width/dx,
          sy = (border_condition<0 && dy>height)?(dy>1?(height-1.0f)/(dy-1):0):(float)height/dy,
          sz = (border_condition<0 && dz>depth )?(dz>1?(depth-1.0f)/(dz-1) :0):(float)depth/dz,
          sv = (border_condition<0 && dv>dim   )?(dv>1?(dim-1.0f)/(dv-1)   :0):(float)dim/dv;

        unsigned int *const off = new unsigned int[dimmax], *poff;
        float *const foff = new float[dimmax], *pfoff, old, curr;
        CImg<T> resx, resy, resz, resv;
        T *ptrd;

        if (dx!=width) {
          if (width==1) resx = get_resize(dx,height,depth,dim,1,0);
          else {
            resx.assign(dx,height,depth,dim);
            curr = old = 0; poff = off; pfoff = foff;
            cimg_forX(resx,x) { *(pfoff++) = curr-(unsigned int)curr; old = curr; curr+=sx; *(poff++) = (unsigned int)curr-(unsigned int)old; }
            ptrd = resx.data;
            const T *ptrs0 = data;
            cimg_forYZV(resx,y,z,k) {
              poff = off; pfoff = foff;
              const T *ptrs = ptrs0, *const ptrsmax = ptrs0 + (width-1);
              cimg_forX(resx,x) {
                const float alpha = *(pfoff++);
                const T val1 = *ptrs, val2 = ptrs<ptrsmax?*(ptrs+1):(border_condition?val1:(T)0);
                *(ptrd++) = (T)((1-alpha)*val1 + alpha*val2);
                ptrs+=*(poff++);
              }
              ptrs0+=width;
            }
          }
        } else resx.assign(*this,true);

        if (dy!=height) {
          if (height==1) resy = resx.get_resize(dx,dy,depth,dim,1,0);
          else {
            resy.assign(dx,dy,depth,dim);
            curr = old = 0; poff = off; pfoff = foff;
            cimg_forY(resy,y) { *(pfoff++) = curr-(unsigned int)curr; old = curr; curr+=sy; *(poff++) = dx*((unsigned int)curr-(unsigned int)old); }
            cimg_forXZV(resy,x,z,k) {
              ptrd = resy.ptr(x,0,z,k);
              const T *ptrs = resx.ptr(x,0,z,k), *const ptrsmax = ptrs + (height-1)*dx;
              poff = off; pfoff = foff;
              cimg_forY(resy,y) {
                const float alpha = *(pfoff++);
                const T val1 = *ptrs, val2 = ptrs<ptrsmax?*(ptrs+dx):(border_condition?val1:(T)0);
                *ptrd = (T)((1-alpha)*val1 + alpha*val2);
                ptrd+=dx;
                ptrs+=*(poff++);
              }
            }
          }
          resx.assign();
        } else resy.assign(resx,true);

        if (dz!=depth) {
          if (depth==1) resz = resy.get_resize(dx,dy,dz,dim,1,0);
          else {
            const unsigned int wh = dx*dy;
            resz.assign(dx,dy,dz,dim);
            curr = old = 0; poff = off; pfoff = foff;
            cimg_forZ(resz,z) { *(pfoff++) = curr-(unsigned int)curr; old = curr; curr+=sz; *(poff++) = wh*((unsigned int)curr-(unsigned int)old); }
            cimg_forXYV(resz,x,y,k) {
              ptrd = resz.ptr(x,y,0,k);
              const T *ptrs = resy.ptr(x,y,0,k), *const ptrsmax = ptrs + (depth-1)*wh;
              poff = off; pfoff = foff;
              cimg_forZ(resz,z) {
                const float alpha = *(pfoff++);
                const T val1 = *ptrs, val2 = ptrs<ptrsmax?*(ptrs+wh):(border_condition?val1:(T)0);
                *ptrd = (T)((1-alpha)*val1 + alpha*val2);
                ptrd+=wh;
                ptrs+=*(poff++);
              }
            }
          }
          resy.assign();
        } else resz.assign(resy,true);

        if (dv!=dim) {
          if (dim==1) resv = resz.get_resize(dx,dy,dz,dv,1,0);
          else {
            const unsigned int whd = dx*dy*dz;
            resv.assign(dx,dy,dz,dv);
            curr = old = 0; poff = off; pfoff = foff;
            cimg_forV(resv,k) { *(pfoff++) = curr-(unsigned int)curr; old = curr; curr+=sv; *(poff++) = whd*((unsigned int)curr-(unsigned int)old); }
            cimg_forXYZ(resv,x,y,z) {
              ptrd = resv.ptr(x,y,z,0);
              const T *ptrs = resz.ptr(x,y,z,0), *const ptrsmax = ptrs + (dim-1)*whd;
              poff = off; pfoff = foff;
              cimg_forV(resv,k) {
                const float alpha = *(pfoff++);
                const T val1 = *ptrs, val2 = ptrs<ptrsmax?*(ptrs+whd):(border_condition?val1:(T)0);
                *ptrd = (T)((1-alpha)*val1 + alpha*val2);
                ptrd+=whd;
                ptrs+=*(poff++);
              }
            }
          }
          resz.assign();
        } else resv.assign(resz,true);

        delete[] off; delete[] foff;
        return resv.is_shared?(resz.is_shared?(resy.is_shared?(resx.is_shared?(+(*this)):resx):resy):resz):resv;
      } break;

      case 4 : { // Grid filling
        res.assign(dx,dy,dz,dv,0);
        cimg_forXYZV(*this,x,y,z,k) res(x*dx/width,y*dy/height,z*dz/depth,k*dv/dim) = (*this)(x,y,z,k);
      } break;

      case 5 : { // Cubic interpolation
        const float
          sx = (border_condition<0 && dx>width )?(dx>1?(width-1.0f)/(dx-1) :0):(float)width/dx,
          sy = (border_condition<0 && dy>height)?(dy>1?(height-1.0f)/(dy-1):0):(float)height/dy,
          sz = (border_condition<0 && dz>depth )?(dz>1?(depth-1.0f)/(dz-1) :0):(float)depth/dz,
          sv = (border_condition<0 && dv>dim   )?(dv>1?(dim-1.0f)/(dv-1)   :0):(float)dim/dv;
        res.assign(dx,dy,dz,dv);
        T *ptrd = res.ptr();
        float cx, cy, cz, ck = 0;
        cimg_forV(res,k) { cz = 0;
        cimg_forZ(res,z) { cy = 0;
        cimg_forY(res,y) { cx = 0;
        cimg_forX(res,x) {
          *(ptrd++) = (T)(border_condition?_cubic_atXY(cx,cy,(int)cz,(int)ck):cubic_atXY(cx,cy,(int)cz,(int)ck,0));
          cx+=sx;
        } cy+=sy;
        } cz+=sz;
        } ck+=sv;
        }
      } break;

      default : // Invalid interpolation method
        throw CImgArgumentException("CImg<%s>::resize() : Invalid interpolation_type %d "
                                    "(should be { -1=raw, 0=zero, 1=nearest, 2=average, 3=linear, 4=grid, 5=bicubic}).",
                                    pixel_type(),interpolation_type);
      }
      return res;
    }

    //! Resize an image.
    /**
       \param src  Image giving the geometry of the resize.
       \param interpolation_type  Interpolation method :
       - 1 = raw memory
       - 0 = no interpolation : additional space is filled with 0.
       - 1 = bloc interpolation (nearest point).
       - 2 = mosaic : image is repeated if necessary.
       - 3 = linear interpolation.
       - 4 = grid interpolation.
       - 5 = bi-cubic interpolation.
       \param border_condition Border condition type.
       \note If pd[x,y,z,v]<0, it corresponds to a percentage of the original size (the default value is -100).
    **/
    template<typename t>
    CImg<T>& resize(const CImg<t>& src, const int interpolation_type=1,
                    const int border_condition=-1, const bool center=false) {
      return resize(src.width,src.height,src.depth,src.dim,interpolation_type,border_condition,center);
    }

    template<typename t>
    CImg<T> get_resize(const CImg<t>& src, const int interpolation_type=1,
                       const int border_condition=-1, const bool center=false) const {
      return get_resize(src.width,src.height,src.depth,src.dim,interpolation_type,border_condition,center);
    }

    //! Resize an image.
    /**
       \param disp = Display giving the geometry of the resize.
       \param interpolation_type = Resizing type :
       - 0 = no interpolation : additional space is filled with 0.
       - 1 = bloc interpolation (nearest point).
       - 2 = mosaic : image is repeated if necessary.
       - 3 = linear interpolation.
       - 4 = grid interpolation.
       - 5 = bi-cubic interpolation.
       - 6 = moving average (best quality for photographs)
       \param border_condition Border condition type.
       \note If pd[x,y,z,v]<0, it corresponds to a percentage of the original size (the default value is -100).
    **/
    CImg<T>& resize(const CImgDisplay& disp, const int interpolation_type=1,
                    const int border_condition=-1, const bool center=false) {
      return resize(disp.width,disp.height,depth,dim,interpolation_type,border_condition,center);
    }

    CImg<T> get_resize(const CImgDisplay& disp, const int interpolation_type=1,
                       const int border_condition=-1, const bool center=false) const {
      return get_resize(disp.width,disp.height,depth,dim,interpolation_type,border_condition,center);
    }

    //! Half-resize an image, using a special optimized filter.
    CImg<T>& resize_halfXY() {
      return get_resize_halfXY().transfer_to(*this);
    }

    CImg<T> get_resize_halfXY() const {
      if (is_empty()) return *this;
      const Tfloat mask[9] = { 0.07842776544f, 0.1231940459f, 0.07842776544f,
                              0.1231940459f,  0.1935127547f, 0.1231940459f,
                              0.07842776544f, 0.1231940459f, 0.07842776544f };
      T I[9] = { 0 };
      CImg<T> dest(width/2,height/2,depth,dim);
      cimg_forZV(*this,z,k) cimg_for3x3(*this,x,y,z,k,I)
        if (x%2 && y%2) dest(x/2,y/2,z,k) = (T)
                          (I[0]*mask[0] + I[1]*mask[1] + I[2]*mask[2] +
                           I[3]*mask[3] + I[4]*mask[4] + I[5]*mask[5] +
                           I[6]*mask[6] + I[7]*mask[7] + I[8]*mask[8]);
      return dest;
    }

    //! Upscale an image by a factor 2x.
    /**
       Use anisotropic upscaling algorithm described at
       http://scale2x.sourceforge.net/algorithm.html
    **/
    CImg<T>& resize_doubleXY() {
      return get_resize_doubleXY().transfer_to(*this);
    }

    CImg<T> get_resize_doubleXY() const {
#define _cimg_gs2x_for3(bound,i) \
 for (int i = 0, _p1##i = 0, \
      _n1##i = 1>=(bound)?(int)(bound)-1:1; \
      _n1##i<(int)(bound) || i==--_n1##i; \
      _p1##i = i++, ++_n1##i, ptrd1+=(res).width, ptrd2+=(res).width)

#define _cimg_gs2x_for3x3(img,x,y,z,v,I) \
  _cimg_gs2x_for3((img).height,y) for (int x = 0, \
   _p1##x = 0, \
   _n1##x = (int)( \
   (I[1] = (img)(0,_p1##y,z,v)), \
   (I[3] = I[4] = (img)(0,y,z,v)), \
   (I[7] = (img)(0,_n1##y,z,v)),        \
   1>=(img).width?(int)((img).width)-1:1); \
   (_n1##x<(int)((img).width) && ( \
   (I[2] = (img)(_n1##x,_p1##y,z,v)), \
   (I[5] = (img)(_n1##x,y,z,v)), \
   (I[8] = (img)(_n1##x,_n1##y,z,v)),1)) || \
   x==--_n1##x; \
   I[1] = I[2], \
   I[3] = I[4], I[4] = I[5], \
   I[7] = I[8], \
   _p1##x = x++, ++_n1##x)

      if (is_empty()) return *this;
      CImg<T> res(2*width,2*height,depth,dim);
      CImg_3x3(I,T);
      cimg_forZV(*this,z,k) {
        T
          *ptrd1 = res.ptr(0,0,0,k),
          *ptrd2 = ptrd1 + res.width;
        _cimg_gs2x_for3x3(*this,x,y,0,k,I) {
          if (Icp!=Icn && Ipc!=Inc) {
            *(ptrd1++) = Ipc==Icp?Ipc:Icc;
            *(ptrd1++) = Icp==Inc?Inc:Icc;
            *(ptrd2++) = Ipc==Icn?Ipc:Icc;
            *(ptrd2++) = Icn==Inc?Inc:Icc;
          } else { *(ptrd1++) = Icc; *(ptrd1++) = Icc; *(ptrd2++) = Icc; *(ptrd2++) = Icc; }
        }
      }
      return res;
    }

    //! Upscale an image by a factor 3x.
    /**
       Use anisotropic upscaling algorithm described at
       http://scale2x.sourceforge.net/algorithm.html
    **/
    CImg<T>& resize_tripleXY() {
      return get_resize_tripleXY().transfer_to(*this);
    }

    CImg<T> get_resize_tripleXY() const {
#define _cimg_gs3x_for3(bound,i) \
 for (int i = 0, _p1##i = 0, \
      _n1##i = 1>=(bound)?(int)(bound)-1:1; \
      _n1##i<(int)(bound) || i==--_n1##i; \
      _p1##i = i++, ++_n1##i, ptrd1+=2*(res).width, ptrd2+=2*(res).width, ptrd3+=2*(res).width)

#define _cimg_gs3x_for3x3(img,x,y,z,v,I) \
  _cimg_gs3x_for3((img).height,y) for (int x = 0, \
   _p1##x = 0, \
   _n1##x = (int)( \
   (I[0] = I[1] = (img)(0,_p1##y,z,v)), \
   (I[3] = I[4] = (img)(0,y,z,v)), \
   (I[6] = I[7] = (img)(0,_n1##y,z,v)), \
   1>=(img).width?(int)((img).width)-1:1); \
   (_n1##x<(int)((img).width) && ( \
   (I[2] = (img)(_n1##x,_p1##y,z,v)), \
   (I[5] = (img)(_n1##x,y,z,v)), \
   (I[8] = (img)(_n1##x,_n1##y,z,v)),1)) || \
   x==--_n1##x; \
   I[0] = I[1], I[1] = I[2], \
   I[3] = I[4], I[4] = I[5], \
   I[6] = I[7], I[7] = I[8], \
   _p1##x = x++, ++_n1##x)

      if (is_empty()) return *this;
      CImg<T> res(3*width,3*height,depth,dim);
      CImg_3x3(I,T);
      cimg_forZV(*this,z,k) {
        T
          *ptrd1 = res.ptr(0,0,0,k),
          *ptrd2 = ptrd1 + res.width,
          *ptrd3 = ptrd2 + res.width;
        _cimg_gs3x_for3x3(*this,x,y,0,k,I) {
          if (Icp != Icn && Ipc != Inc) {
            *(ptrd1++) = Ipc==Icp?Ipc:Icc;
            *(ptrd1++) = (Ipc==Icp && Icc!=Inp) || (Icp==Inc && Icc!=Ipp)?Icp:Icc;
            *(ptrd1++) = Icp==Inc?Inc:Icc;
            *(ptrd2++) = (Ipc==Icp && Icc!=Ipn) || (Ipc==Icn && Icc!=Ipp)?Ipc:Icc;
            *(ptrd2++) = Icc;
            *(ptrd2++) = (Icp==Inc && Icc!=Inn) || (Icn==Inc && Icc!=Inp)?Inc:Icc;
            *(ptrd3++) = Ipc==Icn?Ipc:Icc;
            *(ptrd3++) = (Ipc==Icn && Icc!=Inn) || (Icn==Inc && Icc!=Ipn)?Icn:Icc;
            *(ptrd3++) = Icn==Inc?Inc:Icc;
          } else {
            *(ptrd1++) = Icc; *(ptrd1++) = Icc; *(ptrd1++) = Icc;
            *(ptrd2++) = Icc; *(ptrd2++) = Icc; *(ptrd2++) = Icc;
            *(ptrd3++) = Icc; *(ptrd3++) = Icc; *(ptrd3++) = Icc;
          }
        }
      }
      return res;
    }

    // Warp an image.
    template<typename t>
    CImg<T>& warp(const CImg<t>& warp, const bool relative=false,
                  const bool interpolation=true, const unsigned int border_conditions=0) {
      return get_warp(warp,relative,interpolation,border_conditions).transfer_to(*this);
    }

    template<typename t>
    CImg<T> get_warp(const CImg<t>& warp, const bool relative=false,
                     const bool interpolation=true, const unsigned int border_conditions=0) const {
      if (is_empty() || !warp) return *this;
      if (!is_sameXYZ(warp))
        throw CImgArgumentException("CImg<%s>::warp() : Instance image (%u,%u,%u,%u,%p) and warping field (%u,%u,%u,%u,%p) "
                                    "have different XYZ dimensions.",
                                    pixel_type(),width,height,depth,dim,data,
                                    warp.width,warp.height,warp.depth,warp.dim,warp.data);
      CImg<T> res(width,height,depth,dim);
      switch (warp.dim) {
      case 1 : // 1D warping.
        if (relative) { // Relative warp coordinates
          if (interpolation) switch (border_conditions) {
          case 2 : {
            cimg_forXYZV(*this,x,y,z,v)
              res(x,y,z,v) = (T)_linear_atX(cimg::mod(x-(float)warp(x,y,z,0),(float)width),y,z,v);
          } break;
          case 1 : {
            cimg_forXYZV(*this,x,y,z,v)
              res(x,y,z,v) = (T)_linear_atX(x-(float)warp(x,y,z,0),y,z,v);
          } break;
          default : {
            cimg_forXYZV(*this,x,y,z,v)
              res(x,y,z,v) = (T)linear_atX(x-(float)warp(x,y,z,0),y,z,v,0);
          }
          } else switch (border_conditions) {
          case 2 : {
            cimg_forXYZV(*this,x,y,z,v)
              res(x,y,z,v) = (*this)(cimg::mod(x-(int)warp(x,y,z,0),(int)width),y,z,v);
          } break;
          case 1 : {
            cimg_forXYZV(*this,x,y,z,v)
              res(x,y,z,v) = _atX(x-(int)warp(x,y,z,0),y,z,v);
          } break;
          default : {
            cimg_forXYZV(*this,x,y,z,v)
              res(x,y,z,v) = atX(x-(int)warp(x,y,z,0),y,z,v,0);
          }
          }
        } else { // Absolute warp coordinates
          if (interpolation) switch (border_conditions) {
          case 2 : {
            cimg_forXYZV(*this,x,y,z,v)
              res(x,y,z,v) = (T)_linear_atX(cimg::mod((float)warp(x,y,z,0),(float)width),y,z,v);
          } break;
          case 1 : {
            cimg_forXYZV(*this,x,y,z,v)
              res(x,y,z,v) = (T)_linear_atX((float)warp(x,y,z,0),y,z,v);
          } break;
          default : {
            cimg_forXYZV(*this,x,y,z,v)
              res(x,y,z,v) = (T)linear_atX((float)warp(x,y,z,0),y,z,v,0);
          }
          } else switch (border_conditions) {
          case 2 : {
            cimg_forXYZV(*this,x,y,z,v)
              res(x,y,z,v) = (*this)(cimg::mod((int)warp(x,y,z,0),(int)width),y,z,v);
          } break;
          case 1 : {
            cimg_forXYZV(*this,x,y,z,v)
              res(x,y,z,v) = _atX((int)warp(x,y,z,0),y,z,v);
          } break;
          default : {
            cimg_forXYZV(*this,x,y,z,v)
              res(x,y,z,v) = atX((int)warp(x,y,z,0),y,z,v,0);
          }
          }
        }
        break;

      case 2 : // 2D warping
        if (relative) { // Relative warp coordinates
          if (interpolation) switch (border_conditions) {
          case 2 : {
            cimg_forXYZV(*this,x,y,z,v)
              res(x,y,z,v) = (T)_linear_atXY(cimg::mod(x-(float)warp(x,y,z,0),(float)width),
                                             cimg::mod(y-(float)warp(x,y,z,1),(float)height),z,v);
          } break;
          case 1 : {
            cimg_forXYZV(*this,x,y,z,v)
              res(x,y,z,v) = (T)_linear_atXY(x-(float)warp(x,y,z,0),y-(float)warp(x,y,z,1),z,v);
          } break;
          default : {
            cimg_forXYZV(*this,x,y,z,v)
              res(x,y,z,v) = (T)linear_atXY(x-(float)warp(x,y,z,0),y-(float)warp(x,y,z,1),z,v,0);
          }
          } else switch (border_conditions) {
          case 2 : {
            cimg_forXYZV(*this,x,y,z,v)
              res(x,y,z,v) = (*this)(cimg::mod(x-(int)warp(x,y,z,0),(int)width),
                                     cimg::mod(y-(int)warp(x,y,z,1),(int)height),z,v);
          } break;
          case 1 : {
            cimg_forXYZV(*this,x,y,z,v)
              res(x,y,z,v) = _atXY(x-(int)warp(x,y,z,0),y-(int)warp(x,y,z,1),z,v);
          } break;
          default : {
            cimg_forXYZV(*this,x,y,z,v)
              res(x,y,z,v) = atXY(x-(int)warp(x,y,z,0),y-(int)warp(x,y,z,1),z,v,0);
          }
          }
        } else { // Absolute warp coordinates
          if (interpolation) switch (border_conditions) {
          case 2 : {
            cimg_forXYZV(*this,x,y,z,v)
              res(x,y,z,v) = (T)_linear_atXY(cimg::mod((float)warp(x,y,z,0),(float)width),
                                             cimg::mod((float)warp(x,y,z,1),(float)height),z,v);
          } break;
          case 1 : {
            cimg_forXYZV(*this,x,y,z,v)
              res(x,y,z,v) = (T)_linear_atXY((float)warp(x,y,z,0),(float)warp(x,y,z,1),z,v);
          } break;
          default : {
            cimg_forXYZV(*this,x,y,z,v)
              res(x,y,z,v) = (T)linear_atXY((float)warp(x,y,z,0),(float)warp(x,y,z,1),z,v,0);
          }
          } else switch (border_conditions) {
          case 2 : {
            cimg_forXYZV(*this,x,y,z,v)
              res(x,y,z,v) = (*this)(cimg::mod((int)warp(x,y,z,0),(int)width),
                                     cimg::mod((int)warp(x,y,z,1),(int)depth),z,v);
          } break;
          case 1 : {
            cimg_forXYZV(*this,x,y,z,v)
              res(x,y,z,v) = _atXY((int)warp(x,y,z,0),(int)warp(x,y,z,1),z,v);
          } break;
          default : {
            cimg_forXYZV(*this,x,y,z,v)
              res(x,y,z,v) = atXY((int)warp(x,y,z,0),(int)warp(x,y,z,1),z,v,0);
          }
          }
        }
        break;

      case 3 : // 3D warping
        if (relative) { // Relative warp coordinates
          if (interpolation) switch (border_conditions) {
          case 2 : {
            cimg_forXYZV(*this,x,y,z,v)
              res(x,y,z,v) = (T)_linear_atXYZ(cimg::mod(x-(float)warp(x,y,z,0),(float)width),
                                              cimg::mod(y-(float)warp(x,y,z,1),(float)height),
                                              cimg::mod(z-(float)warp(x,y,z,2),(float)depth),v);
          } break;
          case 1 : {
            cimg_forXYZV(*this,x,y,z,v)
              res(x,y,z,v) = (T)_linear_atXYZ(x-(float)warp(x,y,z,0),y-(float)warp(x,y,z,1),z-(float)warp(x,y,z,2),v);
          } break;
          default : {
            cimg_forXYZV(*this,x,y,z,v)
              res(x,y,z,v) = (T)linear_atXYZ(x-(float)warp(x,y,z,0),y-(float)warp(x,y,z,1),z-(float)warp(x,y,z,2),v,0);
          }
          } else switch (border_conditions) {
          case 2 : {
            cimg_forXYZV(*this,x,y,z,v)
              res(x,y,z,v) = (*this)(cimg::mod(x-(int)warp(x,y,z,0),(int)width),
                                     cimg::mod(y-(int)warp(x,y,z,1),(int)height),
                                     cimg::mod(z-(int)warp(x,y,z,2),(int)depth),v);
          } break;
          case 1 : {
            cimg_forXYZV(*this,x,y,z,v)
              res(x,y,z,v) = _atXYZ(x-(int)warp(x,y,z,0),y-(int)warp(x,y,z,1),z-(int)warp(x,y,z,2),v);
          } break;
          default : {
            cimg_forXYZV(*this,x,y,z,v)
              res(x,y,z,v) = atXYZ(x-(int)warp(x,y,z,0),y-(int)warp(x,y,z,1),z-(int)warp(x,y,z,2),v,0);
          }
          }
        } else { // Absolute warp coordinates
          if (interpolation) switch (border_conditions) {
          case 2 : {
            cimg_forXYZV(*this,x,y,z,v)
              res(x,y,z,v) = (T)_linear_atXYZ(cimg::mod((float)warp(x,y,z,0),(float)width),
                                              cimg::mod((float)warp(x,y,z,1),(float)height),
                                              cimg::mod((float)warp(x,y,z,2),(float)depth),v);
          } break;
          case 1 : {
            cimg_forXYZV(*this,x,y,z,v)
              res(x,y,z,v) = (T)_linear_atXYZ((float)warp(x,y,z,0),(float)warp(x,y,z,1),(float)warp(x,y,z,2),v);
          } break;
          default : {
            cimg_forXYZV(*this,x,y,z,v)
              res(x,y,z,v) = (T)linear_atXYZ((float)warp(x,y,z,0),(float)warp(x,y,z,1),(float)warp(x,y,z,2),v,0);
          }
          } else switch (border_conditions) {
          case 2 : {
            cimg_forXYZV(*this,x,y,z,v)
              res(x,y,z,v) = (*this)(cimg::mod((int)warp(x,y,z,0),(int)width),
                                     cimg::mod((int)warp(x,y,z,1),(int)height),
                                     cimg::mod((int)warp(x,y,z,2),(int)depth),v);
          } break;
          case 1 : {
            cimg_forXYZV(*this,x,y,z,v)
              res(x,y,z,v) = _atXYZ((int)warp(x,y,z,0),(int)warp(x,y,z,1),(int)warp(x,y,z,2),v);
          } break;
          default : {
            cimg_forXYZV(*this,x,y,z,v)
              res(x,y,z,v) = atXYZ((int)warp(x,y,z,0),(int)warp(x,y,z,1),(int)warp(x,y,z,2),v,0);
          }
          }
        }
        break;

      default : // 4D warping
        if (relative) { // Relative warp coordinates
          if (interpolation) switch (border_conditions) {
          case 2 : {
            cimg_forXYZV(*this,x,y,z,v)
              res(x,y,z,v) = (T)_linear_atXYZV(cimg::mod(x-(float)warp(x,y,z,0),(float)width),
                                               cimg::mod(y-(float)warp(x,y,z,1),(float)height),
                                               cimg::mod(z-(float)warp(x,y,z,2),(float)depth),
                                               cimg::mod(z-(float)warp(x,y,z,3),(float)dim));
          } break;
          case 1 : {
            cimg_forXYZV(*this,x,y,z,v)
              res(x,y,z,v) = (T)_linear_atXYZV(x-(float)warp(x,y,z,0),y-(float)warp(x,y,z,1),z-(float)warp(x,y,z,2),v-(float)warp(x,y,z,3));
          } break;
          default : {
            cimg_forXYZV(*this,x,y,z,v)
              res(x,y,z,v) = (T)linear_atXYZV(x-(float)warp(x,y,z,0),y-(float)warp(x,y,z,1),z-(float)warp(x,y,z,2),v-(float)warp(x,y,z,3),0);
          }
          } else switch (border_conditions) {
          case 2 : {
            cimg_forXYZV(*this,x,y,z,v)
              res(x,y,z,v) = (*this)(cimg::mod(x-(int)warp(x,y,z,0),(int)width),
                                     cimg::mod(y-(int)warp(x,y,z,1),(int)height),
                                     cimg::mod(z-(int)warp(x,y,z,2),(int)depth),
                                     cimg::mod(v-(int)warp(x,y,z,3),(int)dim));
          } break;
          case 1 : {
            cimg_forXYZV(*this,x,y,z,v)
              res(x,y,z,v) = _atXYZV(x-(int)warp(x,y,z,0),y-(int)warp(x,y,z,1),z-(int)warp(x,y,z,2),v-(int)warp(x,y,z,3));
          } break;
          default : {
            cimg_forXYZV(*this,x,y,z,v)
              res(x,y,z,v) = atXYZ(x-(int)warp(x,y,z,0),y-(int)warp(x,y,z,1),z-(int)warp(x,y,z,2),v-(int)warp(x,y,z,3),0);
          }
          }
        } else { // Absolute warp coordinates
          if (interpolation) switch (border_conditions) {
          case 2 : {
            cimg_forXYZV(*this,x,y,z,v)
              res(x,y,z,v) = (T)_linear_atXYZV(cimg::mod((float)warp(x,y,z,0),(float)width),
                                               cimg::mod((float)warp(x,y,z,1),(float)height),
                                               cimg::mod((float)warp(x,y,z,2),(float)depth),
                                               cimg::mod((float)warp(x,y,z,3),(float)dim));
          } break;
          case 1 : {
            cimg_forXYZV(*this,x,y,z,v)
              res(x,y,z,v) = (T)_linear_atXYZV((float)warp(x,y,z,0),(float)warp(x,y,z,1),(float)warp(x,y,z,2),(float)warp(x,y,z,3));
          } break;
          default : {
            cimg_forXYZV(*this,x,y,z,v)
              res(x,y,z,v) = (T)linear_atXYZV((float)warp(x,y,z,0),(float)warp(x,y,z,1),(float)warp(x,y,z,2),(float)warp(x,y,z,3),0);
          }
          } else switch (border_conditions) {
          case 2 : {
            cimg_forXYZV(*this,x,y,z,v)
              res(x,y,z,v) = (*this)(cimg::mod((int)warp(x,y,z,0),(int)width),
                                     cimg::mod((int)warp(x,y,z,1),(int)height),
                                     cimg::mod((int)warp(x,y,z,2),(int)depth),
                                     cimg::mod((int)warp(x,y,z,3),(int)dim));
          } break;
          case 1 : {
            cimg_forXYZV(*this,x,y,z,v)
              res(x,y,z,v) = _atXYZV((int)warp(x,y,z,0),(int)warp(x,y,z,1),(int)warp(x,y,z,2),(int)warp(x,y,z,3));
          } break;
          default : {
            cimg_forXYZV(*this,x,y,z,v)
              res(x,y,z,v) = atXYZV((int)warp(x,y,z,0),(int)warp(x,y,z,1),(int)warp(x,y,z,2),(int)warp(x,y,z,3),0);
          }
          }
        }
      }
      return res;
    }

    // Permute axes order (internal).
    template<typename t>
    CImg<t> _get_permute_axes(const char *permut, const t&) const {
      if (is_empty() || !permut) return CImg<t>(*this,false);
      CImg<t> res;
      const T* ptrs = data;
      if (!cimg::strncasecmp(permut,"xyzv",4)) return (+*this);
      if (!cimg::strncasecmp(permut,"xyvz",4)) {
        res.assign(width,height,dim,depth);
        cimg_forXYZV(*this,x,y,z,v) res(x,y,v,z) = (t)*(ptrs++);
      }
      if (!cimg::strncasecmp(permut,"xzyv",4)) {
        res.assign(width,depth,height,dim);
        cimg_forXYZV(*this,x,y,z,v) res(x,z,y,v) = (t)*(ptrs++);
      }
      if (!cimg::strncasecmp(permut,"xzvy",4)) {
        res.assign(width,depth,dim,height);
        cimg_forXYZV(*this,x,y,z,v) res(x,z,v,y) = (t)*(ptrs++);
      }
      if (!cimg::strncasecmp(permut,"xvyz",4)) {
        res.assign(width,dim,height,depth);
        cimg_forXYZV(*this,x,y,z,v) res(x,v,y,z) = (t)*(ptrs++);
      }
      if (!cimg::strncasecmp(permut,"xvzy",4)) {
        res.assign(width,dim,depth,height);
        cimg_forXYZV(*this,x,y,z,v) res(x,v,z,y) = (t)*(ptrs++);
      }
      if (!cimg::strncasecmp(permut,"yxzv",4)) {
        res.assign(height,width,depth,dim);
        cimg_forXYZV(*this,x,y,z,v) res(y,x,z,v) = (t)*(ptrs++);
      }
      if (!cimg::strncasecmp(permut,"yxvz",4)) {
        res.assign(height,width,dim,depth);
        cimg_forXYZV(*this,x,y,z,v) res(y,x,v,z) = (t)*(ptrs++);
      }
      if (!cimg::strncasecmp(permut,"yzxv",4)) {
        res.assign(height,depth,width,dim);
        cimg_forXYZV(*this,x,y,z,v) res(y,z,x,v) = (t)*(ptrs++);
      }
      if (!cimg::strncasecmp(permut,"yzvx",4)) {
        res.assign(height,depth,dim,width);
        switch (width) {
        case 1 : {
          t *ptrR = res.ptr(0,0,0,0);
          for (unsigned long siz = height*depth*dim; siz; --siz) {
            *(ptrR++) = (t)*(ptrs++);
          }
        } break;
        case 2 : {
          t *ptrR = res.ptr(0,0,0,0), *ptrG = res.ptr(0,0,0,1);
          for (unsigned long siz = height*depth*dim; siz; --siz) {
            *(ptrR++) = (t)*(ptrs++); *(ptrG++) = (t)*(ptrs++);
          }
        } break;
        case 3 : { // Optimization for the classical conversion from interleaved RGB to planar RGB
          t *ptrR = res.ptr(0,0,0,0), *ptrG = res.ptr(0,0,0,1), *ptrB = res.ptr(0,0,0,2);
          for (unsigned long siz = height*depth*dim; siz; --siz) {
            *(ptrR++) = (t)*(ptrs++); *(ptrG++) = (t)*(ptrs++); *(ptrB++) = (t)*(ptrs++);
          }
        } break;
        case 4 : { // Optimization for the classical conversion from interleaved RGBA to planar RGBA
          t *ptrR = res.ptr(0,0,0,0), *ptrG = res.ptr(0,0,0,1), *ptrB = res.ptr(0,0,0,2), *ptrA = res.ptr(0,0,0,3);
          for (unsigned long siz = height*depth*dim; siz; --siz) {
            *(ptrR++) = (t)*(ptrs++); *(ptrG++) = (t)*(ptrs++); *(ptrB++) = (t)*(ptrs++); *(ptrA++) = (t)*(ptrs++);
          }
        } break;
        default : {
          cimg_forXYZV(*this,x,y,z,v) res(y,z,v,x) = *(ptrs++);
          return res;
        }
        }
      }
      if (!cimg::strncasecmp(permut,"yvxz",4)) {
        res.assign(height,dim,width,depth);
        cimg_forXYZV(*this,x,y,z,v) res(y,v,x,z) = (t)*(ptrs++);
      }
      if (!cimg::strncasecmp(permut,"yvzx",4)) {
        res.assign(height,dim,depth,width);
        cimg_forXYZV(*this,x,y,z,v) res(y,v,z,x) = (t)*(ptrs++);
      }
      if (!cimg::strncasecmp(permut,"zxyv",4)) {
        res.assign(depth,width,height,dim);
        cimg_forXYZV(*this,x,y,z,v) res(z,x,y,v) = (t)*(ptrs++);
      }
      if (!cimg::strncasecmp(permut,"zxvy",4)) {
        res.assign(depth,width,dim,height);
        cimg_forXYZV(*this,x,y,z,v) res(z,x,v,y) = (t)*(ptrs++);
      }
      if (!cimg::strncasecmp(permut,"zyxv",4)) {
        res.assign(depth,height,width,dim);
        cimg_forXYZV(*this,x,y,z,v) res(z,y,x,v) = (t)*(ptrs++);
      }
      if (!cimg::strncasecmp(permut,"zyvx",4)) {
        res.assign(depth,height,dim,width);
        cimg_forXYZV(*this,x,y,z,v) res(z,y,v,x) = (t)*(ptrs++);
      }
      if (!cimg::strncasecmp(permut,"zvxy",4)) {
        res.assign(depth,dim,width,height);
        cimg_forXYZV(*this,x,y,z,v) res(z,v,x,y) = (t)*(ptrs++);
      }
      if (!cimg::strncasecmp(permut,"zvyx",4)) {
        res.assign(depth,dim,height,width);
        cimg_forXYZV(*this,x,y,z,v) res(z,v,y,x) = (t)*(ptrs++);
      }
      if (!cimg::strncasecmp(permut,"vxyz",4)) {
        res.assign(dim,width,height,depth);
        switch (dim) {
        case 1 : {
          const T *ptrR = ptr(0,0,0,0);
          t *ptrd = res.ptr();
          for (unsigned long siz = width*height*depth; siz; --siz) {
            *(ptrd++) = (t)*(ptrR++);
          }
        } break;
        case 2 : {
          const T *ptrR = ptr(0,0,0,0), *ptrG = ptr(0,0,0,1);
          t *ptrd = res.ptr();
          for (unsigned long siz = width*height*depth; siz; --siz) {
            *(ptrd++) = (t)*(ptrR++); *(ptrd++) = (t)*(ptrG++);
          }
        } break;
        case 3 : { // Optimization for the classical conversion from planar RGB to interleaved RGB
          const T *ptrR = ptr(0,0,0,0), *ptrG = ptr(0,0,0,1), *ptrB = ptr(0,0,0,2);
          t *ptrd = res.ptr();
          for (unsigned long siz = width*height*depth; siz; --siz) {
            *(ptrd++) = (t)*(ptrR++); *(ptrd++) = (t)*(ptrG++); *(ptrd++) = (t)*(ptrB++);
          }
        } break;
        case 4 : { // Optimization for the classical conversion from planar RGBA to interleaved RGBA
          const T *ptrR = ptr(0,0,0,0), *ptrG = ptr(0,0,0,1), *ptrB = ptr(0,0,0,2), *ptrA = ptr(0,0,0,3);
          t *ptrd = res.ptr();
          for (unsigned long siz = width*height*depth; siz; --siz) {
            *(ptrd++) = (t)*(ptrR++); *(ptrd++) = (t)*(ptrG++); *(ptrd++) = (t)*(ptrB++); *(ptrd++) = (t)*(ptrA++);
          }
        } break;
        default : {
          cimg_forXYZV(*this,x,y,z,v) res(v,x,y,z) = (t)*(ptrs++);
        }
        }
      }
      if (!cimg::strncasecmp(permut,"vxzy",4)) {
        res.assign(dim,width,depth,height);
        cimg_forXYZV(*this,x,y,z,v) res(v,x,z,y) = (t)*(ptrs++);
      }
      if (!cimg::strncasecmp(permut,"vyxz",4)) {
        res.assign(dim,height,width,depth);
        cimg_forXYZV(*this,x,y,z,v) res(v,y,x,z) = (t)*(ptrs++);
      }
      if (!cimg::strncasecmp(permut,"vyzx",4)) {
        res.assign(dim,height,depth,width);
        cimg_forXYZV(*this,x,y,z,v) res(v,y,z,x) = (t)*(ptrs++);
      }
      if (!cimg::strncasecmp(permut,"vzxy",4)) {
        res.assign(dim,depth,width,height);
        cimg_forXYZV(*this,x,y,z,v) res(v,z,x,y) = (t)*(ptrs++);
      }
      if (!cimg::strncasecmp(permut,"vzyx",4)) {
        res.assign(dim,depth,height,width);
        cimg_forXYZV(*this,x,y,z,v) res(v,z,y,x) = (t)*(ptrs++);
      }
      if (!res)
        throw CImgArgumentException("CImg<%s>::permute_axes() : Invalid input permutation '%s'.",
                                    pixel_type(),permut);
      return res;
    }

    //! Permute axes order.
    /**
       This function permutes image axes.
       \param permut = String describing the permutation (4 characters).
    **/
    CImg<T>& permute_axes(const char *order) {
      return get_permute_axes(order).transfer_to(*this);
    }

    CImg<T> get_permute_axes(const char *order) const {
      const T foo = (T)0;
      return _get_permute_axes(order,foo);
    }

    //! Invert endianness.
    CImg<T>& invert_endianness() {
      cimg::invert_endianness(data,size());
      return *this;
    }

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

    //! Mirror an image along the specified axis.
    CImg<T>& mirror(const char axis) {
      if (is_empty()) return *this;
      T *pf, *pb, *buf = 0;
      switch (cimg::uncase(axis)) {
      case 'x' : {
        pf = data; pb = ptr(width-1);
        const unsigned int width2 = width/2;
        for (unsigned int yzv = 0; yzv<height*depth*dim; ++yzv) {
          for (unsigned int x = 0; x<width2; ++x) { const T val = *pf; *(pf++) = *pb; *(pb--) = val; }
          pf+=width - width2;
          pb+=width + width2;
        }
      } break;
      case 'y' : {
        buf = new T[width];
        pf = data; pb = ptr(0,height-1);
        const unsigned int height2 = height/2;
        for (unsigned int zv=0; zv<depth*dim; ++zv) {
          for (unsigned int y=0; y<height2; ++y) {
            cimg_std::memcpy(buf,pf,width*sizeof(T));
            cimg_std::memcpy(pf,pb,width*sizeof(T));
            cimg_std::memcpy(pb,buf,width*sizeof(T));
            pf+=width;
            pb-=width;
          }
          pf+=width*(height - height2);
          pb+=width*(height + height2);
        }
      } break;
      case 'z' : {
        buf = new T[width*height];
        pf = data; pb = ptr(0,0,depth-1);
        const unsigned int depth2 = depth/2;
        cimg_forV(*this,v) {
          for (unsigned int z=0; z<depth2; ++z) {
            cimg_std::memcpy(buf,pf,width*height*sizeof(T));
            cimg_std::memcpy(pf,pb,width*height*sizeof(T));
            cimg_std::memcpy(pb,buf,width*height*sizeof(T));
            pf+=width*height;
            pb-=width*height;
          }
          pf+=width*height*(depth - depth2);
          pb+=width*height*(depth + depth2);
        }
      } break;
      case 'v' : {
        buf = new T[width*height*depth];
        pf = data; pb = ptr(0,0,0,dim-1);
        const unsigned int dim2 = dim/2;
        for (unsigned int v=0; v<dim2; ++v) {
          cimg_std::memcpy(buf,pf,width*height*depth*sizeof(T));
          cimg_std::memcpy(pf,pb,width*height*depth*sizeof(T));
          cimg_std::memcpy(pb,buf,width*height*depth*sizeof(T));
          pf+=width*height*depth;
          pb-=width*height*depth;
        }
      } break;
      default :
        throw CImgArgumentException("CImg<%s>::mirror() : unknow axis '%c', must be 'x','y','z' or 'v'.",
                                    pixel_type(),axis);
      }
      if (buf) delete[] buf;
      return *this;
    }

    CImg<T> get_mirror(const char axis) const {
      return (+*this).mirror(axis);
    }

    //! Translate the image.
    /**
       \param deltax Amount of displacement along the X-axis.
       \param deltay Amount of displacement along the Y-axis.
       \param deltaz Amount of displacement along the Z-axis.
       \param deltav Amount of displacement along the V-axis.
       \param border_condition Border condition.

       - \c border_condition can be :
          - 0 : Zero border condition (Dirichlet).
          - 1 : Nearest neighbors (Neumann).
          - 2 : Repeat Pattern (Fourier style).
    **/
    CImg<T>& translate(const int deltax, const int deltay=0, const int deltaz=0, const int deltav=0,
                       const int border_condition=0) {
      if (is_empty()) return *this;
      if (deltax) // Translate along X-axis
        switch (border_condition) {
        case 0 :
          if (cimg::abs(deltax)>=dimx()) return fill(0);
          if (deltax>0) cimg_forYZV(*this,y,z,k) {
            cimg_std::memmove(ptr(0,y,z,k),ptr(deltax,y,z,k),(width-deltax)*sizeof(T));
            cimg_std::memset(ptr(width-deltax,y,z,k),0,deltax*sizeof(T));
          } else cimg_forYZV(*this,y,z,k) {
            cimg_std::memmove(ptr(-deltax,y,z,k),ptr(0,y,z,k),(width+deltax)*sizeof(T));
            cimg_std::memset(ptr(0,y,z,k),0,-deltax*sizeof(T));
          }
          break;
        case 1 :
          if (deltax>0) {
            const int ndeltax = (deltax>=dimx())?width-1:deltax;
            if (!ndeltax) return *this;
            cimg_forYZV(*this,y,z,k) {
              cimg_std::memmove(ptr(0,y,z,k),ptr(ndeltax,y,z,k),(width-ndeltax)*sizeof(T));
              T *ptrd = ptr(width-1,y,z,k);
              const T val = *ptrd;
              for (int l = 0; l<ndeltax-1; ++l) *(--ptrd) = val;
            }
          } else {
            const int ndeltax = (-deltax>=dimx())?width-1:-deltax;
            if (!ndeltax) return *this;
            cimg_forYZV(*this,y,z,k) {
              cimg_std::memmove(ptr(ndeltax,y,z,k),ptr(0,y,z,k),(width-ndeltax)*sizeof(T));
              T *ptrd = ptr(0,y,z,k);
              const T val = *ptrd;
              for (int l = 0; l<ndeltax-1; ++l) *(++ptrd) = val;
            }
          }
          break;
        case 2 : {
          const int ml = cimg::mod(deltax,dimx()), ndeltax = (ml<=dimx()/2)?ml:(ml-dimx());
          if (!ndeltax) return *this;
          T* buf = new T[cimg::abs(ndeltax)];
          if (ndeltax>0) cimg_forYZV(*this,y,z,k) {
            cimg_std::memcpy(buf,ptr(0,y,z,k),ndeltax*sizeof(T));
            cimg_std::memmove(ptr(0,y,z,k),ptr(ndeltax,y,z,k),(width-ndeltax)*sizeof(T));
            cimg_std::memcpy(ptr(width-ndeltax,y,z,k),buf,ndeltax*sizeof(T));
          } else cimg_forYZV(*this,y,z,k) {
            cimg_std::memcpy(buf,ptr(width+ndeltax,y,z,k),-ndeltax*sizeof(T));
            cimg_std::memmove(ptr(-ndeltax,y,z,k),ptr(0,y,z,k),(width+ndeltax)*sizeof(T));
            cimg_std::memcpy(ptr(0,y,z,k),buf,-ndeltax*sizeof(T));
          }
          delete[] buf;
        } break;
        }

      if (deltay) // Translate along Y-axis
        switch (border_condition) {
        case 0 :
          if (cimg::abs(deltay)>=dimy()) return fill(0);
          if (deltay>0) cimg_forZV(*this,z,k) {
            cimg_std::memmove(ptr(0,0,z,k),ptr(0,deltay,z,k),width*(height-deltay)*sizeof(T));
            cimg_std::memset(ptr(0,height-deltay,z,k),0,width*deltay*sizeof(T));
          } else cimg_forZV(*this,z,k) {
            cimg_std::memmove(ptr(0,-deltay,z,k),ptr(0,0,z,k),width*(height+deltay)*sizeof(T));
            cimg_std::memset(ptr(0,0,z,k),0,-deltay*width*sizeof(T));
          }
          break;
        case 1 :
          if (deltay>0) {
            const int ndeltay = (deltay>=dimy())?height-1:deltay;
            if (!ndeltay) return *this;
            cimg_forZV(*this,z,k) {
              cimg_std::memmove(ptr(0,0,z,k),ptr(0,ndeltay,z,k),width*(height-ndeltay)*sizeof(T));
              T *ptrd = ptr(0,height-ndeltay,z,k), *ptrs = ptr(0,height-1,z,k);
              for (int l = 0; l<ndeltay-1; ++l) { cimg_std::memcpy(ptrd,ptrs,width*sizeof(T)); ptrd+=width; }
            }
          } else {
            const int ndeltay = (-deltay>=dimy())?height-1:-deltay;
            if (!ndeltay) return *this;
            cimg_forZV(*this,z,k) {
              cimg_std::memmove(ptr(0,ndeltay,z,k),ptr(0,0,z,k),width*(height-ndeltay)*sizeof(T));
              T *ptrd = ptr(0,1,z,k), *ptrs = ptr(0,0,z,k);
              for (int l = 0; l<ndeltay-1; ++l) { cimg_std::memcpy(ptrd,ptrs,width*sizeof(T)); ptrd+=width; }
            }
          }
          break;
        case 2 : {
          const int ml = cimg::mod(deltay,dimy()), ndeltay = (ml<=dimy()/2)?ml:(ml-dimy());
          if (!ndeltay) return *this;
          T* buf = new T[width*cimg::abs(ndeltay)];
          if (ndeltay>0) cimg_forZV(*this,z,k) {
            cimg_std::memcpy(buf,ptr(0,0,z,k),width*ndeltay*sizeof(T));
            cimg_std::memmove(ptr(0,0,z,k),ptr(0,ndeltay,z,k),width*(height-ndeltay)*sizeof(T));
            cimg_std::memcpy(ptr(0,height-ndeltay,z,k),buf,width*ndeltay*sizeof(T));
          } else cimg_forZV(*this,z,k) {
            cimg_std::memcpy(buf,ptr(0,height+ndeltay,z,k),-ndeltay*width*sizeof(T));
            cimg_std::memmove(ptr(0,-ndeltay,z,k),ptr(0,0,z,k),width*(height+ndeltay)*sizeof(T));
            cimg_std::memcpy(ptr(0,0,z,k),buf,-ndeltay*width*sizeof(T));
          }
          delete[] buf;
        } break;
        }

      if (deltaz) // Translate along Z-axis
        switch (border_condition) {
        case 0 :
          if (cimg::abs(deltaz)>=dimz()) return fill(0);
          if (deltaz>0) cimg_forV(*this,k) {
            cimg_std::memmove(ptr(0,0,0,k),ptr(0,0,deltaz,k),width*height*(depth-deltaz)*sizeof(T));
            cimg_std::memset(ptr(0,0,depth-deltaz,k),0,width*height*deltaz*sizeof(T));
          } else cimg_forV(*this,k) {
            cimg_std::memmove(ptr(0,0,-deltaz,k),ptr(0,0,0,k),width*height*(depth+deltaz)*sizeof(T));
            cimg_std::memset(ptr(0,0,0,k),0,-deltaz*width*height*sizeof(T));
          }
          break;
        case 1 :
          if (deltaz>0) {
            const int ndeltaz = (deltaz>=dimz())?depth-1:deltaz;
            if (!ndeltaz) return *this;
            cimg_forV(*this,k) {
              cimg_std::memmove(ptr(0,0,0,k),ptr(0,0,ndeltaz,k),width*height*(depth-ndeltaz)*sizeof(T));
              T *ptrd = ptr(0,0,depth-ndeltaz,k), *ptrs = ptr(0,0,depth-1,k);
              for (int l = 0; l<ndeltaz-1; ++l) { cimg_std::memcpy(ptrd,ptrs,width*height*sizeof(T)); ptrd+=width*height; }
            }
          } else {
            const int ndeltaz = (-deltaz>=dimz())?depth-1:-deltaz;
            if (!ndeltaz) return *this;
            cimg_forV(*this,k) {
              cimg_std::memmove(ptr(0,0,ndeltaz,k),ptr(0,0,0,k),width*height*(depth-ndeltaz)*sizeof(T));
              T *ptrd = ptr(0,0,1,k), *ptrs = ptr(0,0,0,k);
              for (int l = 0; l<ndeltaz-1; ++l) { cimg_std::memcpy(ptrd,ptrs,width*height*sizeof(T)); ptrd+=width*height; }
            }
          }
          break;
        case 2 : {
          const int ml = cimg::mod(deltaz,dimz()), ndeltaz = (ml<=dimz()/2)?ml:(ml-dimz());
          if (!ndeltaz) return *this;
          T* buf = new T[width*height*cimg::abs(ndeltaz)];
          if (ndeltaz>0) cimg_forV(*this,k) {
            cimg_std::memcpy(buf,ptr(0,0,0,k),width*height*ndeltaz*sizeof(T));
            cimg_std::memmove(ptr(0,0,0,k),ptr(0,0,ndeltaz,k),width*height*(depth-ndeltaz)*sizeof(T));
            cimg_std::memcpy(ptr(0,0,depth-ndeltaz,k),buf,width*height*ndeltaz*sizeof(T));
          } else cimg_forV(*this,k) {
            cimg_std::memcpy(buf,ptr(0,0,depth+ndeltaz,k),-ndeltaz*width*height*sizeof(T));
            cimg_std::memmove(ptr(0,0,-ndeltaz,k),ptr(0,0,0,k),width*height*(depth+ndeltaz)*sizeof(T));
            cimg_std::memcpy(ptr(0,0,0,k),buf,-ndeltaz*width*height*sizeof(T));
          }
          delete[] buf;
        } break;
        }

      if (deltav) // Translate along V-axis
        switch (border_condition) {
        case 0 :
          if (cimg::abs(deltav)>=dimv()) return fill(0);
          if (deltav>0) {
            cimg_std::memmove(data,ptr(0,0,0,deltav),width*height*depth*(dim-deltav)*sizeof(T));
            cimg_std::memset(ptr(0,0,0,dim-deltav),0,width*height*depth*deltav*sizeof(T));
          } else cimg_forV(*this,k) {
            cimg_std::memmove(ptr(0,0,0,-deltav),data,width*height*depth*(dim+deltav)*sizeof(T));
            cimg_std::memset(data,0,-deltav*width*height*depth*sizeof(T));
          }
          break;
        case 1 :
          if (deltav>0) {
            const int ndeltav = (deltav>=dimv())?dim-1:deltav;
            if (!ndeltav) return *this;
            cimg_std::memmove(data,ptr(0,0,0,ndeltav),width*height*depth*(dim-ndeltav)*sizeof(T));
            T *ptrd = ptr(0,0,0,dim-ndeltav), *ptrs = ptr(0,0,0,dim-1);
            for (int l = 0; l<ndeltav-1; ++l) { cimg_std::memcpy(ptrd,ptrs,width*height*depth*sizeof(T)); ptrd+=width*height*depth; }
          } else {
            const int ndeltav = (-deltav>=dimv())?dim-1:-deltav;
            if (!ndeltav) return *this;
            cimg_std::memmove(ptr(0,0,0,ndeltav),data,width*height*depth*(dim-ndeltav)*sizeof(T));
            T *ptrd = ptr(0,0,0,1);
            for (int l = 0; l<ndeltav-1; ++l) { cimg_std::memcpy(ptrd,data,width*height*depth*sizeof(T)); ptrd+=width*height*depth; }
          }
          break;
        case 2 : {
          const int ml = cimg::mod(deltav,dimv()), ndeltav = (ml<=dimv()/2)?ml:(ml-dimv());
          if (!ndeltav) return *this;
          T* buf = new T[width*height*depth*cimg::abs(ndeltav)];
          if (ndeltav>0) {
            cimg_std::memcpy(buf,data,width*height*depth*ndeltav*sizeof(T));
            cimg_std::memmove(data,ptr(0,0,0,ndeltav),width*height*depth*(dim-ndeltav)*sizeof(T));
            cimg_std::memcpy(ptr(0,0,0,dim-ndeltav),buf,width*height*depth*ndeltav*sizeof(T));
          } else {
            cimg_std::memcpy(buf,ptr(0,0,0,dim+ndeltav),-ndeltav*width*height*depth*sizeof(T));
            cimg_std::memmove(ptr(0,0,0,-ndeltav),data,width*height*depth*(dim+ndeltav)*sizeof(T));
            cimg_std::memcpy(data,buf,-ndeltav*width*height*depth*sizeof(T));
          }
          delete[] buf;
        } break;
        }
      return *this;
    }

    CImg<T> get_translate(const int deltax, const int deltay=0, const int deltaz=0, const int deltav=0,
                          const int border_condition=0) const {
      return (+*this).translate(deltax,deltay,deltaz,deltav,border_condition);
    }

    //! Get a square region of the image.
    /**
       \param x0 = X-coordinate of the upper-left crop rectangle corner.
       \param y0 = Y-coordinate of the upper-left crop rectangle corner.
       \param z0 = Z-coordinate of the upper-left crop rectangle corner.
       \param v0 = V-coordinate of the upper-left crop rectangle corner.
       \param x1 = X-coordinate of the lower-right crop rectangle corner.
       \param y1 = Y-coordinate of the lower-right crop rectangle corner.
       \param z1 = Z-coordinate of the lower-right crop rectangle corner.
       \param v1 = V-coordinate of the lower-right crop rectangle corner.
       \param border_condition = Dirichlet (false) or Neumann border conditions.
    **/
    CImg<T>& crop(const int x0, const int y0, const int z0, const int v0,
                  const int x1, const int y1, const int z1, const int v1,
                  const bool border_condition=false) {
      return get_crop(x0,y0,z0,v0,x1,y1,z1,v1,border_condition).transfer_to(*this);
    }

    CImg<T> get_crop(const int x0, const int y0, const int z0, const int v0,
                     const int x1, const int y1, const int z1, const int v1,
                     const bool border_condition=false) const {
      if (is_empty()) return *this;
      const int
        nx0 = x0<x1?x0:x1, nx1 = x0^x1^nx0,
        ny0 = y0<y1?y0:y1, ny1 = y0^y1^ny0,
        nz0 = z0<z1?z0:z1, nz1 = z0^z1^nz0,
        nv0 = v0<v1?v0:v1, nv1 = v0^v1^nv0;
      CImg<T> dest(1U+nx1-nx0,1U+ny1-ny0,1U+nz1-nz0,1U+nv1-nv0);
      if (nx0<0 || nx1>=dimx() || ny0<0 || ny1>=dimy() || nz0<0 || nz1>=dimz() || nv0<0 || nv1>=dimv()) {
        if (border_condition) cimg_forXYZV(dest,x,y,z,v) dest(x,y,z,v) = _atXYZV(nx0+x,ny0+y,nz0+z,nv0+v);
        else dest.fill(0).draw_image(-nx0,-ny0,-nz0,-nv0,*this);
      } else dest.draw_image(-nx0,-ny0,-nz0,-nv0,*this);
      return dest;
    }

    //! Get a rectangular part of the instance image.
    /**
       \param x0 = X-coordinate of the upper-left crop rectangle corner.
       \param y0 = Y-coordinate of the upper-left crop rectangle corner.
       \param z0 = Z-coordinate of the upper-left crop rectangle corner.
       \param x1 = X-coordinate of the lower-right crop rectangle corner.
       \param y1 = Y-coordinate of the lower-right crop rectangle corner.
       \param z1 = Z-coordinate of the lower-right crop rectangle corner.
       \param border_condition = determine the type of border condition if
       some of the desired region is outside the image.
    **/
    CImg<T>& crop(const int x0, const int y0, const int z0,
                  const int x1, const int y1, const int z1,
                  const bool border_condition=false) {
      return crop(x0,y0,z0,0,x1,y1,z1,dim-1,border_condition);
    }

    CImg<T> get_crop(const int x0, const int y0, const int z0,
                     const int x1, const int y1, const int z1,
                     const bool border_condition=false) const {
      return get_crop(x0,y0,z0,0,x1,y1,z1,dim-1,border_condition);
    }

    //! Get a rectangular part of the instance image.
    /**
       \param x0 = X-coordinate of the upper-left crop rectangle corner.
       \param y0 = Y-coordinate of the upper-left crop rectangle corner.
       \param x1 = X-coordinate of the lower-right crop rectangle corner.
       \param y1 = Y-coordinate of the lower-right crop rectangle corner.
       \param border_condition = determine the type of border condition if
       some of the desired region is outside the image.
    **/
    CImg<T>& crop(const int x0, const int y0,
                  const int x1, const int y1,
                  const bool border_condition=false) {
      return crop(x0,y0,0,0,x1,y1,depth-1,dim-1,border_condition);
    }

    CImg<T> get_crop(const int x0, const int y0,
                     const int x1, const int y1,
                     const bool border_condition=false) const {
      return get_crop(x0,y0,0,0,x1,y1,depth-1,dim-1,border_condition);
    }

    //! Get a rectangular part of the instance image.
    /**
       \param x0 = X-coordinate of the upper-left crop rectangle corner.
       \param x1 = X-coordinate of the lower-right crop rectangle corner.
       \param border_condition = determine the type of border condition if
       some of the desired region is outside the image.
    **/
    CImg<T>& crop(const int x0, const int x1, const bool border_condition=false) {
      return crop(x0,0,0,0,x1,height-1,depth-1,dim-1,border_condition);
    }

    CImg<T> get_crop(const int x0, const int x1, const bool border_condition=false) const {
      return get_crop(x0,0,0,0,x1,height-1,depth-1,dim-1,border_condition);
    }

    //! Autocrop an image, regarding of the specified backround value.
    CImg<T>& autocrop(const T value, const char *const axes="vzyx") {
      if (is_empty()) return *this;
      const int lmax = cimg::strlen(axes);
      for (int l = 0; l<lmax; ++l) autocrop(value,axes[l]);
      return *this;
    }

    CImg<T> get_autocrop(const T value, const char *const axes="vzyx") const {
      return (+*this).autocrop(value,axes);
    }

    //! Autocrop an image, regarding of the specified backround color.
    CImg<T>& autocrop(const T *const color, const char *const axes="zyx") {
      if (is_empty()) return *this;
      const int lmax = cimg::strlen(axes);
      for (int l = 0; l<lmax; ++l) autocrop(color,axes[l]);
      return *this;
    }

    CImg<T> get_autocrop(const T *const color, const char *const axes="zyx") const {
      return (+*this).autocrop(color,axes);
    }

    //! Autocrop an image, regarding of the specified backround color.
    template<typename t> CImg<T>& autocrop(const CImg<t>& color, const char *const axes="zyx") {
      return get_autocrop(color,axes).transfer_to(*this);
    }

    template<typename t> CImg<T> get_autocrop(const CImg<t>& color, const char *const axes="zyx") const {
      return get_autocrop(color.data,axes);
    }

    //! Autocrop an image along specified axis, regarding of the specified backround value.
    CImg<T>& autocrop(const T value, const char axis) {
      return get_autocrop(value,axis).transfer_to(*this);
    }

    CImg<T> get_autocrop(const T value, const char axis) const {
      if (is_empty()) return *this;
      CImg<T> res;
      const CImg<intT> coords = _get_autocrop(value,axis);
      switch (cimg::uncase(axis)) {
        case 'x' : {
          const int x0 = coords[0], x1 = coords[1];
          if (x0>=0 && x1>=0) res = get_crop(x0,x1);
        } break;
        case 'y' : {
          const int y0 = coords[0], y1 = coords[1];
          if (y0>=0 && y1>=0) res = get_crop(0,y0,width-1,y1);
        } break;
        case 'z' : {
          const int z0 = coords[0], z1 = coords[1];
          if (z0>=0 && z1>=0) res = get_crop(0,0,z0,width-1,height-1,z1);
        } break;
        case 'v' : {
          const int v0 = coords[0], v1 = coords[1];
          if (v0>=0 && v1>=0) res = get_crop(0,0,0,v0,width-1,height-1,depth-1,v1);
        } break;
      }
      return res;
    }

    //! Autocrop an image along specified axis, regarding of the specified backround color.
    CImg<T>& autocrop(const T *const color, const char axis) {
      return get_autocrop(color,axis).transfer_to(*this);
    }

    CImg<T> get_autocrop(const T *const color, const char axis) const {
      if (is_empty()) return *this;
      CImg<T> res;
      switch (cimg::uncase(axis)) {
        case 'x' : {
          int x0 = width, x1 = -1;
          cimg_forV(*this,k) {
            const CImg<intT> coords = get_shared_channel(k)._get_autocrop(color[k],axis);
            const int nx0 = coords[0], nx1 = coords[1];
            if (nx0>=0 && nx1>=0) { x0 = cimg::min(x0,nx0); x1 = cimg::max(x1,nx1); }
          }
          if (x0<=x1) res = get_crop(x0,x1);
        } break;
        case 'y' : {
          int y0 = height, y1 = -1;
          cimg_forV(*this,k) {
            const CImg<intT> coords = get_shared_channel(k)._get_autocrop(color[k],axis);
            const int ny0 = coords[0], ny1 = coords[1];
            if (ny0>=0 && ny1>=0) { y0 = cimg::min(y0,ny0); y1 = cimg::max(y1,ny1); }
          }
          if (y0<=y1) res = get_crop(0,y0,width-1,y1);
        } break;
        case 'z' : {
          int z0 = depth, z1 = -1;
          cimg_forV(*this,k) {
            const CImg<intT> coords = get_shared_channel(k)._get_autocrop(color[k],axis);
            const int nz0 = coords[0], nz1 = coords[1];
            if (nz0>=0 && nz1>=0) { z0 = cimg::min(z0,nz0); z1 = cimg::max(z1,nz1); }
          }
          if (z0<=z1) res = get_crop(0,0,z0,width-1,height-1,z1);
        } break;
      default :
          throw CImgArgumentException("CImg<%s>::autocrop() : Invalid axis '%c', must be 'x','y' or 'z'.",
                                      pixel_type(),axis);
      }
      return res;
    }

    //! Autocrop an image along specified axis, regarding of the specified backround color.
    template<typename t> CImg<T>& autocrop(const CImg<t>& color, const char axis) {
      return get_autocrop(color,axis).transfer_to(*this);
    }

    template<typename t> CImg<T> get_autocrop(const CImg<t>& color, const char axis) const {
      return get_autocrop(color.data,axis);
    }

    CImg<intT> _get_autocrop(const T value, const char axis) const {
      CImg<intT> res;
      int x0 = -1, y0 = -1, z0 = -1, v0 = -1, x1 = -1, y1 = -1, z1 = -1, v1 = -1;
      switch (cimg::uncase(axis)) {
      case 'x' : {
        cimg_forX(*this,x) cimg_forYZV(*this,y,z,v)
          if ((*this)(x,y,z,v)!=value) { x0 = x; x = dimx(); y = dimy(); z = dimz(); v = dimv(); }
        if (x0>=0) {
          for (int x = dimx()-1; x>=0; --x) cimg_forYZV(*this,y,z,v)
            if ((*this)(x,y,z,v)!=value) { x1 = x; x = 0; y = dimy(); z = dimz(); v = dimv(); }
        }
        res = CImg<intT>::vector(x0,x1);
      } break;
      case 'y' : {
        cimg_forY(*this,y) cimg_forXZV(*this,x,z,v)
          if ((*this)(x,y,z,v)!=value) { y0 = y; x = dimx(); y = dimy(); z = dimz(); v = dimv(); }
        if (y0>=0) {
          for (int y = dimy()-1; y>=0; --y) cimg_forXZV(*this,x,z,v)
            if ((*this)(x,y,z,v)!=value) { y1 = y; x = dimx(); y = 0; z = dimz(); v = dimv(); }
        }
        res = CImg<intT>::vector(y0,y1);
      } break;
      case 'z' : {
        cimg_forZ(*this,z) cimg_forXYV(*this,x,y,v)
          if ((*this)(x,y,z,v)!=value) { z0 = z; x = dimx(); y = dimy(); z = dimz(); v = dimv(); }
        if (z0>=0) {
          for (int z = dimz()-1; z>=0; --z) cimg_forXYV(*this,x,y,v)
            if ((*this)(x,y,z,v)!=value) { z1 = z; x = dimx(); y = dimy(); z = 0; v = dimv(); }
        }
        res = CImg<intT>::vector(z0,z1);
      } break;
      case 'v' : {
        cimg_forV(*this,v) cimg_forXYZ(*this,x,y,z)
          if ((*this)(x,y,z,v)!=value) { v0 = v; x = dimx(); y = dimy(); z = dimz(); v = dimv(); }
        if (v0>=0) {
          for (int v = dimv()-1; v>=0; --v) cimg_forXYZ(*this,x,y,z)
            if ((*this)(x,y,z,v)!=value) { v1 = v; x = dimx(); y = dimy(); z = dimz(); v = 0; }
        }
        res = CImg<intT>::vector(v0,v1);
      } break;
      default :
        throw CImgArgumentException("CImg<%s>::autocrop() : unknow axis '%c', must be 'x','y','z' or 'v'",
                                    pixel_type(),axis);
      }
      return res;
    }

    //! Get a set of columns.
    CImg<T>& columns(const unsigned int x0, const unsigned int x1) {
      return get_columns(x0,x1).transfer_to(*this);
    }

    CImg<T> get_columns(const unsigned int x0, const unsigned int x1) const {
      return get_crop((int)x0,0,0,0,(int)x1,dimy()-1,dimz()-1,dimv()-1);
    }

    //! Get one column.
    CImg<T>& column(const unsigned int x0) {
      return columns(x0,x0);
    }

    CImg<T> get_column(const unsigned int x0) const {
      return get_columns(x0,x0);
    }

    //! Get a set of lines.
    CImg<T>& lines(const unsigned int y0, const unsigned int y1) {
      return get_lines(y0,y1).transfer_to(*this);
    }

    CImg<T> get_lines(const unsigned int y0, const unsigned int y1) const {
      return get_crop(0,(int)y0,0,0,dimx()-1,(int)y1,dimz()-1,dimv()-1);
    }

    //! Get a line.
    CImg<T>& line(const unsigned int y0) {
      return lines(y0,y0);
    }

    CImg<T> get_line(const unsigned int y0) const {
      return get_lines(y0,y0);
    }

    //! Get a set of slices.
    CImg<T>& slices(const unsigned int z0, const unsigned int z1) {
      return get_slices(z0,z1).transfer_to(*this);
    }

    CImg<T> get_slices(const unsigned int z0, const unsigned int z1) const {
      return get_crop(0,0,(int)z0,0,dimx()-1,dimy()-1,(int)z1,dimv()-1);
    }

    //! Get a slice.
    CImg<T>& slice(const unsigned int z0) {
      return slices(z0,z0);
    }

    CImg<T> get_slice(const unsigned int z0) const {
      return get_slices(z0,z0);
    }

    //! Get a set of channels.
    CImg<T>& channels(const unsigned int v0, const unsigned int v1) {
      return get_channels(v0,v1).transfer_to(*this);
    }

    CImg<T> get_channels(const unsigned int v0, const unsigned int v1) const {
      return get_crop(0,0,0,(int)v0,dimx()-1,dimy()-1,dimz()-1,(int)v1);
    }

    //! Get a channel.
    CImg<T>& channel(const unsigned int v0) {
      return channels(v0,v0);
    }

    CImg<T> get_channel(const unsigned int v0) const {
      return get_channels(v0,v0);
    }

    //! Get a shared-memory image referencing a set of points of the instance image.
    CImg<T> get_shared_points(const unsigned int x0, const unsigned int x1,
                              const unsigned int y0=0, const unsigned int z0=0, const unsigned int v0=0) {
      const unsigned long beg = offset(x0,y0,z0,v0), end = offset(x1,y0,z0,v0);
      if (beg>end || beg>=size() || end>=size())
        throw CImgArgumentException("CImg<%s>::get_shared_points() : Cannot return a shared-memory subset (%u->%u,%u,%u,%u) from "
                                    "a (%u,%u,%u,%u) image.",
                                    pixel_type(),x0,x1,y0,z0,v0,width,height,depth,dim);
      return CImg<T>(data+beg,x1-x0+1,1,1,1,true);
    }

    const CImg<T> get_shared_points(const unsigned int x0, const unsigned int x1,
                                    const unsigned int y0=0, const unsigned int z0=0, const unsigned int v0=0) const {
      const unsigned long beg = offset(x0,y0,z0,v0), end = offset(x1,y0,z0,v0);
      if (beg>end || beg>=size() || end>=size())
        throw CImgArgumentException("CImg<%s>::get_shared_points() : Cannot return a shared-memory subset (%u->%u,%u,%u,%u) from "
                                    "a (%u,%u,%u,%u) image.",
                                    pixel_type(),x0,x1,y0,z0,v0,width,height,depth,dim);
      return CImg<T>(data+beg,x1-x0+1,1,1,1,true);
    }

    //! Return a shared-memory image referencing a set of lines of the instance image.
    CImg<T> get_shared_lines(const unsigned int y0, const unsigned int y1,
                             const unsigned int z0=0, const unsigned int v0=0) {
      const unsigned long beg = offset(0,y0,z0,v0), end = offset(0,y1,z0,v0);
      if (beg>end || beg>=size() || end>=size())
        throw CImgArgumentException("CImg<%s>::get_shared_lines() : Cannot return a shared-memory subset (0->%u,%u->%u,%u,%u) from "
                                    "a (%u,%u,%u,%u) image.",
                                    pixel_type(),width-1,y0,y1,z0,v0,width,height,depth,dim);
      return CImg<T>(data+beg,width,y1-y0+1,1,1,true);
    }

    const CImg<T> get_shared_lines(const unsigned int y0, const unsigned int y1,
                                   const unsigned int z0=0, const unsigned int v0=0) const {
      const unsigned long beg = offset(0,y0,z0,v0), end = offset(0,y1,z0,v0);
      if (beg>end || beg>=size() || end>=size())
        throw CImgArgumentException("CImg<%s>::get_shared_lines() : Cannot return a shared-memory subset (0->%u,%u->%u,%u,%u) from "
                                    "a (%u,%u,%u,%u) image.",
                                    pixel_type(),width-1,y0,y1,z0,v0,width,height,depth,dim);
      return CImg<T>(data+beg,width,y1-y0+1,1,1,true);
    }

    //! Return a shared-memory image referencing one particular line (y0,z0,v0) of the instance image.
    CImg<T> get_shared_line(const unsigned int y0, const unsigned int z0=0, const unsigned int v0=0) {
      return get_shared_lines(y0,y0,z0,v0);
    }

    const CImg<T> get_shared_line(const unsigned int y0, const unsigned int z0=0, const unsigned int v0=0) const {
      return get_shared_lines(y0,y0,z0,v0);
    }

    //! Return a shared memory image referencing a set of planes (z0->z1,v0) of the instance image.
    CImg<T> get_shared_planes(const unsigned int z0, const unsigned int z1, const unsigned int v0=0) {
      const unsigned long beg = offset(0,0,z0,v0), end = offset(0,0,z1,v0);
      if (beg>end || beg>=size() || end>=size())
        throw CImgArgumentException("CImg<%s>::get_shared_planes() : Cannot return a shared-memory subset (0->%u,0->%u,%u->%u,%u) from "
                                    "a (%u,%u,%u,%u) image.",
                                    pixel_type(),width-1,height-1,z0,z1,v0,width,height,depth,dim);
      return CImg<T>(data+beg,width,height,z1-z0+1,1,true);
    }

    const CImg<T> get_shared_planes(const unsigned int z0, const unsigned int z1, const unsigned int v0=0) const {
      const unsigned long beg = offset(0,0,z0,v0), end = offset(0,0,z1,v0);
      if (beg>end || beg>=size() || end>=size())
        throw CImgArgumentException("CImg<%s>::get_shared_planes() : Cannot return a shared-memory subset (0->%u,0->%u,%u->%u,%u) from "
                                    "a (%u,%u,%u,%u) image.",
                                    pixel_type(),width-1,height-1,z0,z1,v0,width,height,depth,dim);
      return CImg<T>(data+beg,width,height,z1-z0+1,1,true);
    }

    //! Return a shared-memory image referencing one plane (z0,v0) of the instance image.
    CImg<T> get_shared_plane(const unsigned int z0, const unsigned int v0=0) {
      return get_shared_planes(z0,z0,v0);
    }

    const CImg<T> get_shared_plane(const unsigned int z0, const unsigned int v0=0) const {
      return get_shared_planes(z0,z0,v0);
    }

    //! Return a shared-memory image referencing a set of channels (v0->v1) of the instance image.
    CImg<T> get_shared_channels(const unsigned int v0, const unsigned int v1) {
      const unsigned long beg = offset(0,0,0,v0), end = offset(0,0,0,v1);
      if (beg>end || beg>=size() || end>=size())
        throw CImgArgumentException("CImg<%s>::get_shared_channels() : Cannot return a shared-memory subset (0->%u,0->%u,0->%u,%u->%u) from "
                                    "a (%u,%u,%u,%u) image.",
                                    pixel_type(),width-1,height-1,depth-1,v0,v1,width,height,depth,dim);
      return CImg<T>(data+beg,width,height,depth,v1-v0+1,true);
    }

    const CImg<T> get_shared_channels(const unsigned int v0, const unsigned int v1) const {
      const unsigned long beg = offset(0,0,0,v0), end = offset(0,0,0,v1);
      if (beg>end || beg>=size() || end>=size())
        throw CImgArgumentException("CImg<%s>::get_shared_channels() : Cannot return a shared-memory subset (0->%u,0->%u,0->%u,%u->%u) from "
                                    "a (%u,%u,%u,%u) image.",
                                    pixel_type(),width-1,height-1,depth-1,v0,v1,width,height,depth,dim);
      return CImg<T>(data+beg,width,height,depth,v1-v0+1,true);
    }

    //! Return a shared-memory image referencing one channel v0 of the instance image.
    CImg<T> get_shared_channel(const unsigned int v0) {
      return get_shared_channels(v0,v0);
    }

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

    //! Return a shared version of the instance image.
    CImg<T> get_shared() {
      return CImg<T>(data,width,height,depth,dim,true);
    }

    const CImg<T> get_shared() const {
      return CImg<T>(data,width,height,depth,dim,true);
    }

    //! Return a 2D representation of a 3D image, with three slices.
    CImg<T>& projections2d(const unsigned int x0, const unsigned int y0, const unsigned int z0,
                           const int dx=-100, const int dy=-100, const int dz=-100) {
      return get_projections2d(x0,y0,z0,dx,dy,dz).transfer_to(*this);
    }

    CImg<T> get_projections2d(const unsigned int x0, const unsigned int y0, const unsigned int z0,
                              const int dx=-100, const int dy=-100, const int dz=-100) const {
      if (is_empty()) return *this;
      const unsigned int
        nx0 = (x0>=width)?width-1:x0,
        ny0 = (y0>=height)?height-1:y0,
        nz0 = (z0>=depth)?depth-1:z0;
      CImg<T>
        imgxy(width,height,1,dim),
        imgzy(depth,height,1,dim),
        imgxz(width,depth,1,dim);
      { cimg_forXYV(*this,x,y,k) imgxy(x,y,k) = (*this)(x,y,nz0,k); }
      { cimg_forYZV(*this,y,z,k) imgzy(z,y,k) = (*this)(nx0,y,z,k); }
      { cimg_forXZV(*this,x,z,k) imgxz(x,z,k) = (*this)(x,ny0,z,k); }
      imgxy.resize(dx,dy,1,dim,1);
      imgzy.resize(dz,dy,1,dim,1);
      imgxz.resize(dx,dz,1,dim,1);
      return CImg<T>(imgxy.width+imgzy.width,imgxy.height+imgxz.height,1,dim,0).
        draw_image(imgxy).draw_image(imgxy.width,imgzy).draw_image(0,imgxy.height,imgxz);
    }

    //! Compute the image histogram.
    /**
       The histogram H of an image I is a 1D-function where H(x) is the number of
       occurences of the value x in I.
       \param nblevels = Number of different levels of the computed histogram.
       For classical images, this value is 256. You should specify more levels
       if you are working with CImg<float> or images with high range of pixel values.
       \param val_min = Minimum value considered for the histogram computation. All pixel values lower than val_min
       won't be counted.
       \param val_max = Maximum value considered for the histogram computation. All pixel values higher than val_max
       won't be counted.
       \note If val_min==val_max==0 (default values), the function first estimates the minimum and maximum
       pixel values of the current image, then uses these values for the histogram computation.
       \result The histogram is returned as a 1D CImg<float> image H, having a size of (nblevels,1,1,1) such that
       H(0) and H(nblevels-1) are respectively equal to the number of occurences of the values val_min and val_max in I.
       \note Histogram computation always returns a 1D function. Histogram of multi-valued (such as color) images
       are not multi-dimensional.
    **/
    CImg<T>& histogram(const unsigned int nblevels, const T val_min=(T)0, const T val_max=(T)0) {
      return get_histogram(nblevels,val_min,val_max).transfer_to(*this);
    }

    CImg<floatT> get_histogram(const unsigned int nblevels, const T val_min=(T)0, const T val_max=(T)0) const {
      if (is_empty()) return CImg<floatT>();
      if (!nblevels)
        throw CImgArgumentException("CImg<%s>::get_histogram() : Can't compute an histogram with 0 levels",
                                    pixel_type());
      T vmin = val_min, vmax = val_max;
      CImg<floatT> res(nblevels,1,1,1,0);
      if (vmin>=vmax && vmin==0) vmin = minmax(vmax);
      if (vmin<vmax) cimg_for(*this,ptr,T) {
        const int pos = (int)((*ptr-vmin)*(nblevels-1)/(vmax-vmin));
        if (pos>=0 && pos<(int)nblevels) ++res[pos];
      } else res[0]+=size();
      return res;
    }

    //! Compute the histogram-equalized version of the instance image.
    /**
       The histogram equalization is a classical image processing algorithm that enhances the image contrast
       by expanding its histogram.
       \param nblevels = Number of different levels of the computed histogram.
       For classical images, this value is 256. You should specify more levels
       if you are working with CImg<float> or images with high range of pixel values.
       \param val_min = Minimum value considered for the histogram computation. All pixel values lower than val_min
       won't be changed.
       \param val_max = Maximum value considered for the histogram computation. All pixel values higher than val_max
       won't be changed.
       \note If val_min==val_max==0 (default values), the function acts on all pixel values of the image.
       \return A new image with same size is returned, where pixels have been equalized.
    **/
    CImg<T>& equalize(const unsigned int nblevels, const T val_min=(T)0, const T val_max=(T)0) {
      if (is_empty()) return *this;
      T vmin = val_min, vmax = val_max;
      if (vmin==vmax && vmin==0) vmin = minmax(vmax);
      if (vmin<vmax) {
        CImg<floatT> hist = get_histogram(nblevels,vmin,vmax);
        float cumul = 0;
        cimg_forX(hist,pos) { cumul+=hist[pos]; hist[pos]=cumul; }
        cimg_for(*this,ptr,T) {
          const int pos = (unsigned int)((*ptr-vmin)*(nblevels-1)/(vmax-vmin));
          if (pos>=0 && pos<(int)nblevels) *ptr = (T)(vmin + (vmax-vmin)*hist[pos]/size());
        }
      }
      return *this;
    }

    CImg<T> get_equalize(const unsigned int nblevels, const T val_min=(T)0, const T val_max=(T)0) const {
      return (+*this).equalize(nblevels,val_min,val_max);
    }

    //! Get a label map of disconnected regions with same intensities.
    CImg<T>& label_regions() {
      return get_label_regions().transfer_to(*this);
    }

    CImg<uintT> get_label_regions() const {
#define _cimg_get_label_test(p,q) { \
  flag = true; \
  const T *ptr1 = ptr(x,y) + siz, *ptr2 = ptr(p,q) + siz; \
  for (unsigned int i = dim; flag && i; --i) { ptr1-=wh; ptr2-=wh; flag = (*ptr1==*ptr2); } \
}
      if (depth>1)
        throw CImgInstanceException("CImg<%s>::label_regions() : Instance image must be a 2D image");
      CImg<uintT> res(width,height,depth,1,0);
      unsigned int label = 1;
      const unsigned int wh = width*height, siz = width*height*dim;
      const int W1 = dimx()-1, H1 = dimy()-1;
      bool flag;
      cimg_forXY(*this,x,y) {
        bool done = false;
        if (y) {
          _cimg_get_label_test(x,y-1);
          if (flag) {
            const unsigned int lab = (res(x,y) = res(x,y-1));
            done = true;
            if (x && res(x-1,y)!=lab) {
              _cimg_get_label_test(x-1,y);
              if (flag) {
                const unsigned int lold = res(x-1,y), *const cptr = res.ptr(x,y);
                for (unsigned int *ptr = res.ptr(); ptr<cptr; ++ptr) if (*ptr==lold) *ptr = lab;
              }
            }
          }
        }
        if (x && !done) { _cimg_get_label_test(x-1,y); if (flag) { res(x,y) = res(x-1,y); done = true; }}
        if (!done) res(x,y) = label++;
      }
      { for (int y = H1; y>=0; --y) for (int x=W1; x>=0; --x) {
        bool done = false;
        if (y<H1) {
          _cimg_get_label_test(x,y+1);
          if (flag) {
            const unsigned int lab = (res(x,y) = res(x,y+1));
            done = true;
            if (x<W1 && res(x+1,y)!=lab) {
              _cimg_get_label_test(x+1,y);
              if (flag) {
                const unsigned int lold = res(x+1,y), *const cptr = res.ptr(x,y);
                for (unsigned int *ptr = res.ptr()+res.size()-1; ptr>cptr; --ptr) if (*ptr==lold) *ptr = lab;
              }
            }
          }
        }
        if (x<W1 && !done) { _cimg_get_label_test(x+1,y); if (flag) res(x,y) = res(x+1,y); done = true; }
      }}
      const unsigned int lab0 = res.max()+1;
      label = lab0;
      cimg_foroff(res,off) { // Relabel regions
        const unsigned int lab = res[off];
        if (lab<lab0) { cimg_for(res,ptr,unsigned int) if (*ptr==lab) *ptr = label; ++label; }
      }
      return (res-=lab0);
    }

    //! Compute the scalar image of vector norms.
    /**
       When dealing with vector-valued images (i.e images with dimv()>1), this function computes the L1,L2 or Linf norm of each
       vector-valued pixel.
       \param norm_type = Type of the norm being computed (1 = L1, 2 = L2, -1 = Linf).
       \return A scalar-valued image CImg<float> with size (dimx(),dimy(),dimz(),1), where each pixel is the norm
       of the corresponding pixels in the original vector-valued image.
    **/
    CImg<T>& pointwise_norm(int norm_type=2) {
      return get_pointwise_norm(norm_type).transfer_to(*this);
    }

    CImg<Tfloat> get_pointwise_norm(int norm_type=2) const {
      if (is_empty()) return *this;
      if (dim==1) return get_abs();
      CImg<Tfloat> res(width,height,depth);
      switch (norm_type) {
      case -1 : {             // Linf norm
        cimg_forXYZ(*this,x,y,z) {
          Tfloat n = 0; cimg_forV(*this,v) {
            const Tfloat tmp = (Tfloat)cimg::abs((*this)(x,y,z,v));
            if (tmp>n) n=tmp; res(x,y,z) = n;
          }
        }
      } break;
      case 1 : {              // L1 norm
        cimg_forXYZ(*this,x,y,z) {
          Tfloat n = 0; cimg_forV(*this,v) n+=cimg::abs((*this)(x,y,z,v)); res(x,y,z) = n;
        }
      } break;
      default : {             // L2 norm
        cimg_forXYZ(*this,x,y,z) {
          Tfloat n = 0; cimg_forV(*this,v) n+=(*this)(x,y,z,v)*(*this)(x,y,z,v); res(x,y,z) = (Tfloat)cimg_std::sqrt((double)n);
        }
      }
      }
      return res;
    }

    //! Compute the image of normalized vectors.
    /**
       When dealing with vector-valued images (i.e images with dimv()>1), this function return the image of normalized vectors
       (unit vectors). Null vectors are unchanged. The L2-norm is computed for the normalization.
       \return A new vector-valued image with same size, where each vector-valued pixels have been normalized.
    **/
    CImg<T>& pointwise_orientation() {
      cimg_forXYZ(*this,x,y,z) {
        float n = 0;
        cimg_forV(*this,v) n+=(float)((*this)(x,y,z,v)*(*this)(x,y,z,v));
        n = (float)cimg_std::sqrt(n);
        if (n>0) cimg_forV(*this,v) (*this)(x,y,z,v) = (T)((*this)(x,y,z,v)/n);
        else cimg_forV(*this,v) (*this)(x,y,z,v) = 0;
      }
      return *this;
    }

    CImg<Tfloat> get_pointwise_orientation() const {
      if (is_empty()) return *this;
      return CImg<Tfloat>(*this,false).pointwise_orientation();
    }

    //! Split image into a list.
    CImgList<T> get_split(const char axis, const unsigned int nb=0) const {
      if (is_empty()) return CImgList<T>();
      CImgList<T> res;
      switch (cimg::uncase(axis)) {
      case 'x' : {
        if (nb>width)
          throw CImgArgumentException("CImg<%s>::get_split() : Cannot split instance image (%u,%u,%u,%u,%p) along 'x' into %u images.",
                                      pixel_type(),width,height,depth,dim,data,nb);
        res.assign(nb?nb:width);
        const unsigned int delta = (unsigned int)cimg::round((float)width/res.size,1);
        unsigned int l, x;
        for (l = 0, x = 0; l<res.size-1; ++l, x+=delta) res[l] = get_crop(x,0,0,0,x+delta-1,height-1,depth-1,dim-1);
        res[res.size-1] = get_crop(x,0,0,0,width-1,height-1,depth-1,dim-1);
      } break;
      case 'y' : {
        if (nb>height)
          throw CImgArgumentException("CImg<%s>::get_split() : Cannot split instance image (%u,%u,%u,%u,%p) along 'y' into %u images.",
                                      pixel_type(),width,height,depth,dim,data,nb);
        res.assign(nb?nb:height);
        const unsigned int delta = (unsigned int)cimg::round((float)height/res.size,1);
        unsigned int l, y;
        for (l = 0, y = 0; l<res.size-1; ++l, y+=delta) res[l] = get_crop(0,y,0,0,width-1,y+delta-1,depth-1,dim-1);
        res[res.size-1] = get_crop(0,y,0,0,width-1,height-1,depth-1,dim-1);
      } break;
      case 'z' : {
        if (nb>depth)
          throw CImgArgumentException("CImg<%s>::get_split() : Cannot split instance image (%u,%u,%u,%u,%p) along 'z' into %u images.",
                                      pixel_type(),width,height,depth,dim,data,nb);
        res.assign(nb?nb:depth);
        const unsigned int delta = (unsigned int)cimg::round((float)depth/res.size,1);
        unsigned int l, z;
        for (l = 0, z = 0; l<res.size-1; ++l, z+=delta) res[l] = get_crop(0,0,z,0,width-1,height-1,z+delta-1,dim-1);
        res[res.size-1] = get_crop(0,0,z,0,width-1,height-1,depth-1,dim-1);
      } break;
      case 'v' : {
        if (nb>dim)
          throw CImgArgumentException("CImg<%s>::get_split() : Cannot split instance image (%u,%u,%u,%u,%p) along 'v' into %u images.",
                                      pixel_type(),width,height,depth,dim,data,nb);
        res.assign(nb?nb:dim);
        const unsigned int delta = (unsigned int)cimg::round((float)dim/res.size,1);
        unsigned int l, v;
        for (l = 0, v = 0; l<res.size-1; ++l, v+=delta) res[l] = get_crop(0,0,0,v,width-1,height-1,depth-1,v+delta-1);
        res[res.size-1] = get_crop(0,0,0,v,width-1,height-1,depth-1,dim-1);
      } break;
      default :
        throw CImgArgumentException("CImg<%s>::get_split() : Unknow axis '%c', must be 'x','y','z' or 'v'",
                                    pixel_type(),axis);
      }
      return res;
    }

    // Split image into a list of vectors, according to a given splitting value.
    CImgList<T> get_split(const T value, const bool keep_values, const bool shared) const {
      CImgList<T> res;
      const T *ptr0 = data, *const ptr_end = data + size();
      while (ptr0<ptr_end) {
        const T *ptr1 = ptr0;
        while (ptr1<ptr_end && *ptr1==value) ++ptr1;
        const unsigned int siz0 = ptr1 - ptr0;
        if (siz0 && keep_values) res.insert(CImg<T>(ptr0,1,siz0,1,1,shared));
        ptr0 = ptr1;
        while (ptr1<ptr_end && *ptr1!=value) ++ptr1;
        const unsigned int siz1 = ptr1 - ptr0;
        if (siz1) res.insert(CImg<T>(ptr0,1,siz1,1,1,shared),~0U,shared);
        ptr0 = ptr1;
      }
      return res;
    }

    //! Append an image to another one.
    CImg<T>& append(const CImg<T>& img, const char axis, const char align='p') {
      if (!img) return *this;
      if (is_empty()) return (*this=img);
      return get_append(img,axis,align).transfer_to(*this);
    }

    CImg<T> get_append(const CImg<T>& img, const char axis, const char align='p') const {
      if (!img) return *this;
      if (is_empty()) return img;
      CImgList<T> temp(2);
      temp[0].width = width; temp[0].height = height; temp[0].depth = depth;
      temp[0].dim = dim; temp[0].data = data;
      temp[1].width = img.width; temp[1].height = img.height; temp[1].depth = img.depth;
      temp[1].dim = img.dim; temp[1].data = img.data;
      const CImg<T> res = temp.get_append(axis,align);
      temp[0].width = temp[0].height = temp[0].depth = temp[0].dim = 0; temp[0].data = 0;
      temp[1].width = temp[1].height = temp[1].depth = temp[1].dim = 0; temp[1].data = 0;
      return res;
    }

    //! Compute the list of images, corresponding to the XY-gradients of an image.
    /**
       \param scheme = Numerical scheme used for the gradient computation :
       - -1 = Backward finite differences
       - 0 = Centered finite differences
       - 1 = Forward finite differences
       - 2 = Using Sobel masks
       - 3 = Using rotation invariant masks
       - 4 = Using Deriche recusrsive filter.
    **/
    CImgList<Tfloat> get_gradient(const char *const axes=0, const int scheme=3) const {
      CImgList<Tfloat> grad(2,width,height,depth,dim);
      bool threed = false;
      if (axes) {
        for (unsigned int a = 0; axes[a]; ++a) {
          const char axis = cimg::uncase(axes[a]);
          switch (axis) {
          case 'x' : case 'y' : break;
          case 'z' : threed = true; break;
          default :
            throw CImgArgumentException("CImg<%s>::get_gradient() : Unknown specified axis '%c'.",
                                        pixel_type(),axis);
          }
        }
      } else threed = (depth>1);
      if (threed) {
        grad.insert(1); grad[2].assign(width,height,depth,dim);
        switch (scheme) { // Compute 3D gradient
        case -1 : { // backward finite differences
          CImg_3x3x3(I,T);
          cimg_forV(*this,k) cimg_for3x3x3(*this,x,y,z,k,I) {
            grad[0](x,y,z,k) = (Tfloat)Iccc - Ipcc;
            grad[1](x,y,z,k) = (Tfloat)Iccc - Icpc;
            grad[2](x,y,z,k) = (Tfloat)Iccc - Iccp;
          }
        } break;
        case 1 : { // forward finite differences
          CImg_2x2x2(I,T);
          cimg_forV(*this,k) cimg_for2x2x2(*this,x,y,z,k,I) {
            grad[0](x,y,z,k) = (Tfloat)Incc - Iccc;
            grad[1](x,y,z,k) = (Tfloat)Icnc - Iccc;
            grad[2](x,y,z,k) = (Tfloat)Iccn - Iccc;
          }
        } break;
        case 4 : { // using Deriche filter with low standard variation
          grad[0] = get_deriche(0,1,'x');
          grad[1] = get_deriche(0,1,'y');
          grad[2] = get_deriche(0,1,'z');
        } break;
        default : { // central finite differences
          CImg_3x3x3(I,T);
          cimg_forV(*this,k) cimg_for3x3x3(*this,x,y,z,k,I) {
            grad[0](x,y,z,k) = 0.5f*((Tfloat)Incc - Ipcc);
            grad[1](x,y,z,k) = 0.5f*((Tfloat)Icnc - Icpc);
            grad[2](x,y,z,k) = 0.5f*((Tfloat)Iccn - Iccp);
          }
        }
        }
      } else switch (scheme) { // Compute 2D-gradient
      case -1 : { // backward finite differences
        CImg_3x3(I,T);
        cimg_forZV(*this,z,k) cimg_for3x3(*this,x,y,z,k,I) {
          grad[0](x,y,z,k) = (Tfloat)Icc - Ipc;
          grad[1](x,y,z,k) = (Tfloat)Icc - Icp;
        }
      } break;
      case 1 : { // forward finite differences
        CImg_2x2(I,T);
        cimg_forZV(*this,z,k) cimg_for2x2(*this,x,y,z,k,I) {
          grad[0](x,y,0,k) = (Tfloat)Inc - Icc;
          grad[1](x,y,z,k) = (Tfloat)Icn - Icc;
        }
      } break;
      case 2 : { // using Sobel mask
        CImg_3x3(I,T);
        const Tfloat a = 1, b = 2;
        cimg_forZV(*this,z,k) cimg_for3x3(*this,x,y,z,k,I) {
          grad[0](x,y,z,k) = -a*Ipp - b*Ipc - a*Ipn + a*Inp + b*Inc + a*Inn;
          grad[1](x,y,z,k) = -a*Ipp - b*Icp - a*Inp + a*Ipn + b*Icn + a*Inn;
        }
      } break;
      case 3 : { // using rotation invariant mask
        CImg_3x3(I,T);
        const Tfloat a = (Tfloat)(0.25f*(2-cimg_std::sqrt(2.0f))), b = (Tfloat)(0.5f*(cimg_std::sqrt(2.0f)-1));
        cimg_forZV(*this,z,k) cimg_for3x3(*this,x,y,z,k,I) {
          grad[0](x,y,z,k) = -a*Ipp - b*Ipc - a*Ipn + a*Inp + b*Inc + a*Inn;
          grad[1](x,y,z,k) = -a*Ipp - b*Icp - a*Inp + a*Ipn + b*Icn + a*Inn;
        }
      } break;
      case 4 : { // using Deriche filter with low standard variation
        grad[0] = get_deriche(0,1,'x');
        grad[1] = get_deriche(0,1,'y');
      } break;
      default : { // central finite differences
        CImg_3x3(I,T);
        cimg_forZV(*this,z,k) cimg_for3x3(*this,x,y,z,k,I) {
          grad[0](x,y,z,k) = 0.5f*((Tfloat)Inc - Ipc);
          grad[1](x,y,z,k) = 0.5f*((Tfloat)Icn - Icp);
        }
      }
      }
      if (!axes) return grad;
      CImgList<Tfloat> res;
      for (unsigned int l = 0; axes[l]; ++l) {
        const char axis = cimg::uncase(axes[l]);
        switch (axis) {
        case 'x' : res.insert(grad[0]); break;
        case 'y' : res.insert(grad[1]); break;
        case 'z' : res.insert(grad[2]); break;
        }
      }
      grad.assign();
      return res;
    }

    //! Compute the structure tensor field of an image.
    CImg<T>& structure_tensor(const bool central_scheme=false) {
      return get_structure_tensor(central_scheme).transfer_to(*this);
    }

    CImg<Tfloat> get_structure_tensor(const bool central_scheme=false) const {
      if (is_empty()) return *this;
      CImg<Tfloat> res;
      if (depth>1) { // 3D version
        res.assign(width,height,depth,6,0);
        CImg_3x3x3(I,T);
        if (central_scheme) cimg_forV(*this,k) cimg_for3x3x3(*this,x,y,z,k,I) { // classical central finite differences
          const Tfloat
            ix = 0.5f*((Tfloat)Incc - Ipcc),
            iy = 0.5f*((Tfloat)Icnc - Icpc),
            iz = 0.5f*((Tfloat)Iccn - Iccp);
          res(x,y,z,0)+=ix*ix;
          res(x,y,z,1)+=ix*iy;
          res(x,y,z,2)+=ix*iz;
          res(x,y,z,3)+=iy*iy;
          res(x,y,z,4)+=iy*iz;
          res(x,y,z,5)+=iz*iz;
        } else cimg_forV(*this,k) cimg_for3x3x3(*this,x,y,z,k,I) { // Precise forward/backward finite differences
          const Tfloat
            ixf = (Tfloat)Incc - Iccc, ixb = (Tfloat)Iccc - Ipcc,
            iyf = (Tfloat)Icnc - Iccc, iyb = (Tfloat)Iccc - Icpc,
            izf = (Tfloat)Iccn - Iccc, izb = (Tfloat)Iccc - Iccp;
          res(x,y,z,0) += 0.5f*(ixf*ixf + ixb*ixb);
          res(x,y,z,1) += 0.25f*(ixf*iyf + ixf*iyb + ixb*iyf + ixb*iyb);
          res(x,y,z,2) += 0.25f*(ixf*izf + ixf*izb + ixb*izf + ixb*izb);
          res(x,y,z,3) += 0.5f*(iyf*iyf + iyb*iyb);
          res(x,y,z,4) += 0.25f*(iyf*izf + iyf*izb + iyb*izf + iyb*izb);
          res(x,y,z,5) += 0.5f*(izf*izf + izb*izb);
        }
      } else { // 2D version
        res.assign(width,height,depth,3,0);
        CImg_3x3(I,T);
        if (central_scheme) cimg_forV(*this,k) cimg_for3x3(*this,x,y,0,k,I) { // classical central finite differences
          const Tfloat
            ix = 0.5f*((Tfloat)Inc - Ipc),
            iy = 0.5f*((Tfloat)Icn - Icp);
          res(x,y,0,0)+=ix*ix;
          res(x,y,0,1)+=ix*iy;
          res(x,y,0,2)+=iy*iy;
        } else cimg_forV(*this,k) cimg_for3x3(*this,x,y,0,k,I) { // Precise forward/backward finite differences
          const Tfloat
            ixf = (Tfloat)Inc - Icc, ixb = (Tfloat)Icc - Ipc,
            iyf = (Tfloat)Icn - Icc, iyb = (Tfloat)Icc - Icp;
          res(x,y,0,0) += 0.5f*(ixf*ixf+ixb*ixb);
          res(x,y,0,1) += 0.25f*(ixf*iyf+ixf*iyb+ixb*iyf+ixb*iyb);
          res(x,y,0,2) += 0.5f*(iyf*iyf+iyb*iyb);
        }
      }
      return res;
    }

    //! Get components of the Hessian matrix of an image.
    CImgList<Tfloat> get_hessian(const char *const axes=0) const {
      const char *naxes = axes, *const def_axes2d = "xxxyyy", *const def_axes3d = "xxxyxzyyyzzz";
      if (!axes) naxes = depth>1?def_axes3d:def_axes2d;
      CImgList<Tfloat> res;
      const int lmax = cimg::strlen(naxes);
      if (lmax%2)
        throw CImgArgumentException("CImg<%s>::get_hessian() : Incomplete parameter axes = '%s'.",
                                    pixel_type(),naxes);
      res.assign(lmax/2,width,height,depth,dim);
      if (!cimg::strcasecmp(naxes,def_axes3d)) { // Default 3D version
        CImg_3x3x3(I,T);
        cimg_forV(*this,k) cimg_for3x3x3(*this,x,y,z,k,I) {
          res[0](x,y,z,k) = (Tfloat)Ipcc + Incc - 2*Iccc;              // Ixx
          res[1](x,y,z,k) = 0.25f*((Tfloat)Ippc + Innc - Ipnc - Inpc); // Ixy
          res[2](x,y,z,k) = 0.25f*((Tfloat)Ipcp + Incn - Ipcn - Incp); // Ixz
          res[3](x,y,z,k) = (Tfloat)Icpc + Icnc - 2*Iccc;              // Iyy
          res[4](x,y,z,k) = 0.25f*((Tfloat)Icpp + Icnn - Icpn - Icnp); // Iyz
          res[5](x,y,z,k) = (Tfloat)Iccn + Iccp - 2*Iccc;              // Izz
        }
      } else if (!cimg::strcasecmp(naxes,def_axes2d)) { // Default 2D version
        CImg_3x3(I,T);
        cimg_forV(*this,k) cimg_for3x3(*this,x,y,0,k,I) {
          res[0](x,y,0,k) = (Tfloat)Ipc + Inc - 2*Icc;             // Ixx
          res[1](x,y,0,k) = 0.25f*((Tfloat)Ipp + Inn - Ipn - Inp); // Ixy
          res[2](x,y,0,k) = (Tfloat)Icp + Icn - 2*Icc;             // Iyy
        }
      } else for (int l = 0; l<lmax; ) { // Version with custom axes.
          const int l2 = l/2;
          char axis1 = naxes[l++], axis2 = naxes[l++];
          if (axis1>axis2) cimg::swap(axis1,axis2);
          bool valid_axis = false;
          if (axis1=='x' && axis2=='x') { // Ixx
            valid_axis = true; CImg_3x3(I,T);
            cimg_forZV(*this,z,k) cimg_for3x3(*this,x,y,z,k,I) res[l2](x,y,z,k) = (Tfloat)Ipc + Inc - 2*Icc;
          }
          else if (axis1=='x' && axis2=='y') { // Ixy
            valid_axis = true; CImg_3x3(I,T);
            cimg_forZV(*this,z,k) cimg_for3x3(*this,x,y,z,k,I) res[l2](x,y,z,k) = 0.25f*((Tfloat)Ipp + Inn - Ipn - Inp);
          }
          else if (axis1=='x' && axis2=='z') { // Ixz
            valid_axis = true; CImg_3x3x3(I,T);
            cimg_forV(*this,k) cimg_for3x3x3(*this,x,y,z,k,I) res[l2](x,y,z,k) = 0.25f*((Tfloat)Ipcp + Incn - Ipcn - Incp);
          }
          else if (axis1=='y' && axis2=='y') { // Iyy
            valid_axis = true; CImg_3x3(I,T);
            cimg_forZV(*this,z,k) cimg_for3x3(*this,x,y,z,k,I) res[l2](x,y,z,k) = (Tfloat)Icp + Icn - 2*Icc;
          }
          else if (axis1=='y' && axis2=='z') { // Iyz
            valid_axis = true; CImg_3x3x3(I,T);
            cimg_forV(*this,k) cimg_for3x3x3(*this,x,y,z,k,I) res[l2](x,y,z,k) = 0.25f*((Tfloat)Icpp + Icnn - Icpn - Icnp);
          }
          else if (axis1=='z' && axis2=='z') { // Izz
            valid_axis = true; CImg_3x3x3(I,T);
            cimg_forV(*this,k) cimg_for3x3x3(*this,x,y,z,k,I) res[l2](x,y,z,k) = (Tfloat)Iccn + Iccp - 2*Iccc;
          }
          else if (!valid_axis) throw CImgArgumentException("CImg<%s>::get_hessian() : Invalid parameter axes = '%s'.",
                                                            pixel_type(),naxes);
      }
      return res;
    }

    //! Compute distance function from 0-valued isophotes by the application of an Hamilton-Jacobi PDE.
    CImg<T>& distance_hamilton(const unsigned int nb_iter, const float band_size=0, const float precision=0.5f) {
      if (is_empty()) return *this;
      CImg<Tfloat> veloc(*this);
      for (unsigned int iter = 0; iter<nb_iter; ++iter) {
        veloc.fill(0);
        if (depth>1) { // 3D version
          CImg_3x3x3(I,T);
          cimg_forV(*this,k) cimg_for3x3x3(*this,x,y,z,k,I) if (band_size<=0 || cimg::abs(Iccc)<band_size) {
            const Tfloat
              gx = 0.5f*((Tfloat)Incc - Ipcc),
              gy = 0.5f*((Tfloat)Icnc - Icpc),
              gz = 0.5f*((Tfloat)Iccn - Iccp),
              sgn = -cimg::sign((Tfloat)Iccc),
              ix = gx*sgn>0?(Tfloat)Incc - Iccc:(Tfloat)Iccc - Ipcc,
              iy = gy*sgn>0?(Tfloat)Icnc - Iccc:(Tfloat)Iccc - Icpc,
              iz = gz*sgn>0?(Tfloat)Iccn - Iccc:(Tfloat)Iccc - Iccp,
              ng = 1e-5f + (Tfloat)cimg_std::sqrt(gx*gx + gy*gy + gz*gz),
              ngx = gx/ng,
              ngy = gy/ng,
              ngz = gz/ng;
            veloc(x,y,z,k) = sgn*(ngx*ix + ngy*iy + ngz*iz - 1);
          }
        } else { // 2D version
          CImg_3x3(I,T);
          cimg_forV(*this,k) cimg_for3x3(*this,x,y,0,k,I) if (band_size<=0 || cimg::abs(Icc)<band_size) {
            const Tfloat
              gx = 0.5f*((Tfloat)Inc - Ipc),
              gy = 0.5f*((Tfloat)Icn - Icp),
              sgn = -cimg::sign((Tfloat)Icc),
              ix = gx*sgn>0?(Tfloat)Inc - Icc:(Tfloat)Icc - Ipc,
              iy = gy*sgn>0?(Tfloat)Icn - Icc:(Tfloat)Icc - Icp,
              ng = 1e-5f + (Tfloat)cimg_std::sqrt(gx*gx + gy*gy),
              ngx = gx/ng,
              ngy = gy/ng;
            veloc(x,y,k) = sgn*(ngx*ix + ngy*iy - 1);
          }
        }
        float m, M = (float)veloc.maxmin(m), xdt = precision/(float)cimg::max(cimg::abs(m),cimg::abs(M));
        *this+=(veloc*=xdt);
      }
      return *this;
    }

    CImg<Tfloat> get_distance_hamilton(const unsigned int nb_iter, const float band_size=0, const float precision=0.5f) const {
      return CImg<Tfloat>(*this,false).distance_hamilton(nb_iter,band_size,precision);
    }

    //! Compute the Euclidean distance map to a shape of specified isovalue.
    CImg<T>& distance(const T isovalue,
                      const float sizex=1, const float sizey=1, const float sizez=1,
                      const bool compute_sqrt=true) {
      return get_distance(isovalue,sizex,sizey,sizez,compute_sqrt).transfer_to(*this);
    }

    CImg<floatT> get_distance(const T isovalue,
                              const float sizex=1, const float sizey=1, const float sizez=1,
                              const bool compute_sqrt=true) const {
      if (is_empty()) return *this;
      const int dx = dimx(), dy = dimy(), dz = dimz();
      CImg<floatT> res(dx,dy,dz,dim);
      const float maxdist = (float)cimg_std::sqrt((float)dx*dx + dy*dy + dz*dz);
      cimg_forV(*this,k) {
        bool is_isophote = false;

        if (depth>1) { // 3D version
          { cimg_forYZ(*this,y,z) {
            if ((*this)(0,y,z,k)==isovalue) { is_isophote = true; res(0,y,z,k) = 0; } else res(0,y,z,k) = maxdist;
            for (int x = 1; x<dx; ++x) if ((*this)(x,y,z,k)==isovalue) { is_isophote = true; res(x,y,z,k) = 0; }
            else res(x,y,z,k) = res(x-1,y,z,k) + sizex;
            { for (int x = dx-2; x>=0; --x) if (res(x+1,y,z,k)<res(x,y,z,k)) res(x,y,z,k) = res(x+1,y,z,k) + sizex; }
          }}
          if (!is_isophote) { res.get_shared_channel(k).fill(cimg::type<float>::max()); continue; }
          CImg<floatT> tmp(cimg::max(dy,dz));
          CImg<intT> s(tmp.width), t(s.width);
          { cimg_forXZ(*this,x,z) {
            { cimg_forY(*this,y) tmp[y] = res(x,y,z,k); }
            int q = s[0] = t[0] = 0;
            { for (int y = 1; y<dy; ++y) {
              const float val = tmp[y], val2 = val*val;
              while (q>=0 && _distance_f(t[q],s[q],cimg::sqr(tmp[s[q]]),sizey)>_distance_f(t[q],y,val2,sizey)) --q;
              if (q<0) { q = 0; s[0] = y; }
              else {
                const int w = 1 + _distance_sep(s[q],y,(int)cimg::sqr(tmp[s[q]]),(int)val2,sizey);
                if (w<dy) { s[++q] = y; t[q] = w; }
              }
            }}
            { for (int y = dy - 1; y>=0; --y) {
              res(x,y,z,k) = _distance_f(y,s[q],cimg::sqr(tmp[s[q]]),sizey);
              if (y==t[q]) --q;
            }}
          }}
          { cimg_forXY(*this,x,y) {
            { cimg_forZ(*this,z) tmp[z] = res(x,y,z,k); }
            int q = s[0] = t[0] = 0;
            { for (int z = 1; z<dz; ++z) {
              const float val = tmp[z];
              while (q>=0 && _distance_f(t(q),s[q],tmp[s[q]],sizez)>_distance_f(t[q],z,tmp[z],sizez)) --q;
              if (q<0) { q = 0; s[0] = z; }
              else {
                const int w = 1 + _distance_sep(s[q],z,(int)tmp[s[q]],(int)val,sizez);
                if (w<dz) { s[++q] = z; t[q] = w; }
              }
            }}
            { for (int z = dz - 1; z>=0; --z) {
              const float val = _distance_f(z,s[q],tmp[s[q]],sizez);
              res(x,y,z,k) = compute_sqrt?(float)cimg_std::sqrt(val):val;
              if (z==t[q]) --q;
            }}
          }}
        } else { // 2D version (with small optimizations)
          cimg_forX(*this,x) {
            const T *ptrs = ptr(x,0,0,k);
            float *ptrd = res.ptr(x,0,0,k), d = *ptrd = *ptrs==isovalue?(is_isophote=true),0:maxdist;
            for (int y = 1; y<dy; ++y) { ptrs+=width; ptrd+=width; d = *ptrd = *ptrs==isovalue?(is_isophote=true),0:d+sizey; }
            { for (int y = dy - 2; y>=0; --y) { ptrd-=width; if (d<*ptrd) *ptrd = (d+=sizey); else d = *ptrd; }}
          }
          if (!is_isophote) { res.get_shared_channel(k).fill(cimg::type<float>::max()); continue; }
          CImg<floatT> tmp(dx);
          CImg<intT> s(dx), t(dx);
          cimg_forY(*this,y) {
            float *ptmp = tmp.ptr();
            cimg_std::memcpy(ptmp,res.ptr(0,y,0,k),sizeof(float)*dx);
            int q = s[0] = t[0] = 0;
            for (int x = 1; x<dx; ++x) {
              const float val = *(++ptmp), val2 = val*val;
              while (q>=0 && _distance_f(t[q],s[q],cimg::sqr(tmp[s[q]]),sizex)>_distance_f(t[q],x,val2,sizex)) --q;
              if (q<0) { q = 0; s[0] = x; }
              else {
                const int w = 1 + _distance_sep(s[q],x,(int)cimg::sqr(tmp[s[q]]),(int)val2,sizex);
                if (w<dx) { q++; s[q] = x; t[q] = w; }
              }
            }
            float *pres = res.ptr(0,y,0,k) + width;
            { for (int x = dx - 1; x>=0; --x) {
              const float val = _distance_f(x,s[q],cimg::sqr(tmp[s[q]]),sizex);
              *(--pres) = compute_sqrt?(float)cimg_std::sqrt(val):val;
              if (x==t[q]) --q;
            }}
          }
        }
      }
      return res;
    }

    static float _distance_f(const int x, const int i, const float gi2, const float fact) {
      const float xmi = fact*((float)x - i);
      return xmi*xmi + gi2;
    }
    static int _distance_sep(const int i, const int u, const int gi2, const int gu2, const float fact) {
      const float fact2 = fact*fact;
      return (int)(fact2*(u*u - i*i) + gu2 - gi2)/(int)(2*fact2*(u - i));
    }

    //! Compute minimal path in a graph, using the Dijkstra algorithm.
    /**
       \param distance An object having operator()(unsigned int i, unsigned int j) which returns distance between two nodes (i,j).
       \param nb_nodes Number of graph nodes.
       \param starting_node Indice of the starting node.
       \param ending_node Indice of the ending node (set to ~0U to ignore ending node).
       \param previous Array that gives the previous node indice in the path to the starting node (optional parameter).
       \return Array of distances of each node to the starting node.
    **/
    template<typename tf, typename t>
    static CImg<T> dijkstra(const tf& distance, const unsigned int nb_nodes,
                            const unsigned int starting_node, const unsigned int ending_node,
                            CImg<t>& previous) {

      CImg<T> dist(1,nb_nodes,1,1,cimg::type<T>::max());
      dist(starting_node) = 0;
      previous.assign(1,nb_nodes,1,1,(t)-1);
      previous(starting_node) = (t)starting_node;
      CImg<uintT> Q(nb_nodes);
      cimg_forX(Q,u) Q(u) = u;
      cimg::swap(Q(starting_node),Q(0));
      unsigned int sizeQ = nb_nodes;
      while (sizeQ) {
        // Update neighbors from minimal vertex
        const unsigned int umin = Q(0);
        if (umin==ending_node) sizeQ = 0;
        else {
          const T dmin = dist(umin);
          const T infty = cimg::type<T>::max();
          for (unsigned int q=1; q<sizeQ; ++q) {
            const unsigned int v = Q(q);
            const T d = (T)distance(v,umin);
            if (d<infty) {
              const T alt = dmin + d;
              if (alt<dist(v)) {
                dist(v) = alt;
                previous(v) = (t)umin;
                const T distpos = dist(Q(q));
                for (unsigned int pos = q, par = 0; pos && distpos<dist(Q(par=(pos+1)/2-1)); pos=par) cimg::swap(Q(pos),Q(par));
              }
            }
          }
          // Remove minimal vertex from queue
          Q(0) = Q(--sizeQ);
          const T distpos = dist(Q(0));
          for (unsigned int pos = 0, left = 0, right = 0;
               ((right=2*(pos+1),(left=right-1))<sizeQ && distpos>dist(Q(left))) || (right<sizeQ && distpos>dist(Q(right)));) {
            if (right<sizeQ) {
              if (dist(Q(left))<dist(Q(right))) { cimg::swap(Q(pos),Q(left)); pos = left; }
              else { cimg::swap(Q(pos),Q(right)); pos = right; }
            } else { cimg::swap(Q(pos),Q(left)); pos = left; }
          }
        }
      }
      return dist;
    }

    //! Return minimal path in a graph, using the Dijkstra algorithm.
    template<typename tf, typename t>
    static CImg<T> dijkstra(const tf& distance, const unsigned int nb_nodes,
                            const unsigned int starting_node, const unsigned int ending_node=~0U) {
      CImg<uintT> foo;
      return dijkstra(distance,nb_nodes,starting_node,ending_node,foo);
    }

    //! Return minimal path in a graph, using the Dijkstra algorithm.
    /**
       Instance image corresponds to the adjacency matrix of the graph.
       \param starting_node Indice of the starting node.
       \param previous Array that gives the previous node indice in the path to the starting node (optional parameter).
       \return Array of distances of each node to the starting node.
    **/
    template<typename t>
    CImg<T>& dijkstra(const unsigned int starting_node, const unsigned int ending_node, CImg<t>& previous) {
      return get_dijkstra(starting_node,ending_node,previous).transfer_to(*this);
    }

    template<typename t>
    CImg<T> get_dijkstra(const unsigned int starting_node, const unsigned int ending_node, CImg<t>& previous) const {
      if (width!=height || depth!=1 || dim!=1)
        throw CImgInstanceException("CImg<%s>::dijkstra() : Instance image (%u,%u,%u,%u,%p) is not a graph adjacency matrix",
                                    pixel_type(),width,height,depth,dim,data);
      return dijkstra(*this,width,starting_node,ending_node,previous);
    }

    //! Return minimal path in a graph, using the Dijkstra algorithm.
    CImg<T>& dijkstra(const unsigned int starting_node, const unsigned int ending_node=~0U) {
      return get_dijkstra(starting_node,ending_node).transfer_to(*this);
    }

    CImg<Tfloat> get_dijkstra(const unsigned int starting_node, const unsigned int ending_node=~0U) const {
      CImg<uintT> foo;
      return get_dijkstra(starting_node,ending_node,foo);
    }

    //@}
    //-------------------------------------
    //
    //! \name Meshes and Triangulations
    //@{
    //-------------------------------------

    //! Return a 3D centered cube.
    template<typename tf>
    static CImg<floatT> cube3d(CImgList<tf>& primitives, const float size=100) {
      const double s = size/2.0;
      primitives.assign(6,1,4,1,1, 0,3,2,1, 4,5,6,7, 0,1,5,4, 3,7,6,2, 0,4,7,3, 1,2,6,5);
      return CImg<floatT>(8,3,1,1,
                          -s,s,s,-s,-s,s,s,-s,
                          -s,-s,s,s,-s,-s,s,s,
                          -s,-s,-s,-s,s,s,s,s);
    }

    //! Return a 3D centered cuboid.
    template<typename tf>
    static CImg<floatT> cuboid3d(CImgList<tf>& primitives, const float sizex=200,
                                 const float sizey=100, const float sizez=100) {
      const double sx = sizex/2.0, sy = sizey/2.0, sz = sizez/2.0;
      primitives.assign(6,1,4,1,1, 0,3,2,1, 4,5,6,7, 0,1,5,4, 3,7,6,2, 0,4,7,3, 1,2,6,5);
      return CImg<floatT>(8,3,1,1,
                          -sx,sx,sx,-sx,-sx,sx,sx,-sx,
                          -sy,-sy,sy,sy,-sy,-sy,sy,sy,
                          -sz,-sz,-sz,-sz,sz,sz,sz,sz);
    }

    //! Return a 3D centered cone.
    template<typename tf>
    static CImg<floatT> cone3d(CImgList<tf>& primitives, const float radius=50, const float height=100,
                               const unsigned int subdivisions=24, const bool symetrize=false) {
      primitives.assign();
      if (!subdivisions) return CImg<floatT>();
      const double r = (double)radius, h = (double)height/2;
      CImgList<floatT> points(2,1,3,1,1,
                              0.0,0.0,h,
                              0.0,0.0,-h);
      const float delta = 360.0f/subdivisions, nh = symetrize?0:-(float)h;
      for (float angle = 0; angle<360; angle+=delta) {
        const float a = (float)(angle*cimg::valuePI/180);
        points.insert(CImg<floatT>::vector((float)(r*cimg_std::cos(a)),(float)(r*cimg_std::sin(a)),nh));
      }
      const unsigned int nbr = points.size-2;
      for (unsigned int p = 0; p<nbr; ++p) {
        const unsigned int curr = 2+p, next = 2+((p+1)%nbr);
        primitives.insert(CImg<tf>::vector(1,next,curr)).
          insert(CImg<tf>::vector(0,curr,next));
      }
      return points.get_append('x');
    }

    //! Return a 3D centered cylinder.
    template<typename tf>
    static CImg<floatT> cylinder3d(CImgList<tf>& primitives, const float radius=50, const float height=100,
                                   const unsigned int subdivisions=24) {
      primitives.assign();
      if (!subdivisions) return CImg<floatT>();
      const double r = (double)radius, h = (double)height/2;
      CImgList<floatT> points(2,1,3,1,1,
                              0.0,0.0,-h,
                              0.0,0.0,h);

      const float delta = 360.0f/subdivisions;
      for (float angle = 0; angle<360; angle+=delta) {
        const float a = (float)(angle*cimg::valuePI/180);
        points.insert(CImg<floatT>::vector((float)(r*cimg_std::cos(a)),(float)(r*cimg_std::sin(a)),-(float)h));
        points.insert(CImg<floatT>::vector((float)(r*cimg_std::cos(a)),(float)(r*cimg_std::sin(a)),(float)h));
      }
      const unsigned int nbr = (points.size-2)/2;
      for (unsigned int p = 0; p<nbr; ++p) {
        const unsigned int curr = 2+2*p, next = 2+(2*((p+1)%nbr));
        primitives.insert(CImg<tf>::vector(0,next,curr)).
          insert(CImg<tf>::vector(1,curr+1,next+1)).
          insert(CImg<tf>::vector(curr,next,next+1,curr+1));
      }
      return points.get_append('x');
    }

    //! Return a 3D centered torus.
    template<typename tf>
    static CImg<floatT> torus3d(CImgList<tf>& primitives, const float radius1=100, const float radius2=30,
                                const unsigned int subdivisions1=24, const unsigned int subdivisions2=12) {
      primitives.assign();
      if (!subdivisions1 || !subdivisions2) return CImg<floatT>();
      CImgList<floatT> points;
      for (unsigned int v = 0; v<subdivisions1; ++v) {
        const float
          beta = (float)(v*2*cimg::valuePI/subdivisions1),
          xc = radius1*(float)cimg_std::cos(beta),
          yc = radius1*(float)cimg_std::sin(beta);
        for (unsigned int u=0; u<subdivisions2; ++u) {
          const float
            alpha = (float)(u*2*cimg::valuePI/subdivisions2),
            x = xc + radius2*(float)(cimg_std::cos(alpha)*cimg_std::cos(beta)),
            y = yc + radius2*(float)(cimg_std::cos(alpha)*cimg_std::sin(beta)),
            z = radius2*(float)cimg_std::sin(alpha);
          points.insert(CImg<floatT>::vector(x,y,z));
        }
      }
      for (unsigned int vv = 0; vv<subdivisions1; ++vv) {
        const unsigned int nv = (vv+1)%subdivisions1;
        for (unsigned int uu = 0; uu<subdivisions2; ++uu) {
          const unsigned int nu = (uu+1)%subdivisions2, svv = subdivisions2*vv, snv = subdivisions2*nv;
          primitives.insert(CImg<tf>::vector(svv+nu,svv+uu,snv+uu));
          primitives.insert(CImg<tf>::vector(svv+nu,snv+uu,snv+nu));
        }
      }
      return points.get_append('x');
    }

    //! Return a 3D centered XY plane.
    template<typename tf>
    static CImg<floatT> plane3d(CImgList<tf>& primitives, const float sizex=100, const float sizey=100,
                                const unsigned int subdivisionsx=3, const unsigned int subdivisionsy=3,
                                const bool double_sided=false) {
      primitives.assign();
      if (!subdivisionsx || !subdivisionsy) return CImg<floatT>();
      CImgList<floatT> points;
      const unsigned int w = subdivisionsx + 1, h = subdivisionsy + 1;
      const float w2 = subdivisionsx/2.0f, h2 = subdivisionsy/2.0f, fx = (float)sizex/w, fy = (float)sizey/h;
      for (unsigned int yy = 0; yy<h; ++yy)
        for (unsigned int xx = 0; xx<w; ++xx)
          points.insert(CImg<floatT>::vector(fx*(xx-w2),fy*(yy-h2),0));
      for (unsigned int y = 0; y<subdivisionsy; ++y) for (unsigned int x = 0; x<subdivisionsx; ++x) {
        const int off1 = x+y*w, off2 = x+1+y*w, off3 = x+1+(y+1)*w, off4 = x+(y+1)*w;
        primitives.insert(CImg<tf>::vector(off1,off4,off3,off2));
        if (double_sided) primitives.insert(CImg<tf>::vector(off1,off2,off3,off4));
      }
      return points.get_append('x');
    }

    //! Return a 3D centered sphere.
    template<typename tf>
    static CImg<floatT> sphere3d(CImgList<tf>& primitives, const float radius=50, const unsigned int subdivisions=3) {

      // Create initial icosahedron
      primitives.assign();
      if (!subdivisions) return CImg<floatT>();
      const double tmp = (1+cimg_std::sqrt(5.0f))/2, a = 1.0/cimg_std::sqrt(1+tmp*tmp), b = tmp*a;
      CImgList<floatT> points(12,1,3,1,1, b,a,0.0, -b,a,0.0, -b,-a,0.0, b,-a,0.0, a,0.0,b, a,0.0,-b,
                              -a,0.0,-b, -a,0.0,b, 0.0,b,a, 0.0,-b,a, 0.0,-b,-a, 0.0,b,-a);
      primitives.assign(20,1,3,1,1, 4,8,7, 4,7,9, 5,6,11, 5,10,6, 0,4,3, 0,3,5, 2,7,1, 2,1,6,
                        8,0,11, 8,11,1, 9,10,3, 9,2,10, 8,4,0, 11,0,5, 4,9,3,
                        5,3,10, 7,8,1, 6,1,11, 7,2,9, 6,10,2);

      // Recurse subdivisions
      for (unsigned int i = 0; i<subdivisions; ++i) {
        const unsigned int L = primitives.size;
        for (unsigned int l = 0; l<L; ++l) {
          const unsigned int
            p0 = (unsigned int)primitives(0,0), p1 = (unsigned int)primitives(0,1), p2 = (unsigned int)primitives(0,2);
          const float
            x0 = points(p0,0), y0 = points(p0,1), z0 = points(p0,2),
            x1 = points(p1,0), y1 = points(p1,1), z1 = points(p1,2),
            x2 = points(p2,0), y2 = points(p2,1), z2 = points(p2,2),
            tnx0 = (x0+x1)/2, tny0 = (y0+y1)/2, tnz0 = (z0+z1)/2, nn0 = (float)cimg_std::sqrt(tnx0*tnx0+tny0*tny0+tnz0*tnz0),
            tnx1 = (x0+x2)/2, tny1 = (y0+y2)/2, tnz1 = (z0+z2)/2, nn1 = (float)cimg_std::sqrt(tnx1*tnx1+tny1*tny1+tnz1*tnz1),
            tnx2 = (x1+x2)/2, tny2 = (y1+y2)/2, tnz2 = (z1+z2)/2, nn2 = (float)cimg_std::sqrt(tnx2*tnx2+tny2*tny2+tnz2*tnz2),
            nx0 = tnx0/nn0, ny0 = tny0/nn0, nz0 = tnz0/nn0,
            nx1 = tnx1/nn1, ny1 = tny1/nn1, nz1 = tnz1/nn1,
            nx2 = tnx2/nn2, ny2 = tny2/nn2, nz2 = tnz2/nn2;
          int i0 = -1, i1 = -1, i2 = -1;
          cimglist_for(points,p) {
            const float x = (float)points(p,0), y = (float)points(p,1), z = (float)points(p,2);
            if (x==nx0 && y==ny0 && z==nz0) i0 = p;
            if (x==nx1 && y==ny1 && z==nz1) i1 = p;
            if (x==nx2 && y==ny2 && z==nz2) i2 = p;
          }
          if (i0<0) { points.insert(CImg<floatT>::vector(nx0,ny0,nz0)); i0 = points.size-1; }
          if (i1<0) { points.insert(CImg<floatT>::vector(nx1,ny1,nz1)); i1 = points.size-1; }
          if (i2<0) { points.insert(CImg<floatT>::vector(nx2,ny2,nz2)); i2 = points.size-1; }
          primitives.remove(0);
          primitives.insert(CImg<tf>::vector(p0,i0,i1)).
            insert(CImg<tf>::vector((tf)i0,(tf)p1,(tf)i2)).
            insert(CImg<tf>::vector((tf)i1,(tf)i2,(tf)p2)).
            insert(CImg<tf>::vector((tf)i1,(tf)i0,(tf)i2));
        }
      }
      return points.get_append('x')*=radius;
    }

    //! Return a 3D centered ellipsoid.
    template<typename tf, typename t>
    static CImg<floatT> ellipsoid3d(CImgList<tf>& primitives, const CImg<t>& tensor,
                                    const unsigned int subdivisions=3) {
      primitives.assign();
      if (!subdivisions) return CImg<floatT>();
      typedef typename cimg::superset<t,float>::type tfloat;
      CImg<tfloat> S,V;
      tensor.symmetric_eigen(S,V);
      const tfloat l0 = S[0], l1 = S[1], l2 = S[2];
      CImg<floatT> points = sphere(primitives,subdivisions);
      cimg_forX(points,p) {
        points(p,0) = (float)(points(p,0)*l0);
        points(p,1) = (float)(points(p,1)*l1);
        points(p,2) = (float)(points(p,2)*l2);
      }
      V.transpose();
      points = V*points;
      return points;
    }

    //! Return a 3D elevation object of the instance image.
    template<typename tf, typename tc, typename te>
    CImg<floatT> get_elevation3d(CImgList<tf>& primitives, CImgList<tc>& colors, const CImg<te>& elevation) const {
      primitives.assign();
      colors.assign();
      if (is_empty()) return *this;
      if (depth>1)
        throw CImgInstanceException("CImg<%s>::get_elevation3d() : Instance image (%u,%u,%u,%u,%p) is not a 2D image.",
                                    pixel_type(),width,height,depth,dim,data);
      if (!is_sameXY(elevation))
        throw CImgArgumentException("CImg<%s>::get_elevation3d() : Elevation image (%u,%u,%u,%u,%p) and instance image (%u,%u,%u,%u,%p) "
                                    "have different sizes.",pixel_type(),
                                    elevation.width,elevation.height,elevation.depth,elevation.dim,elevation.data,
                                    width,height,depth,dim,data,pixel_type());
      float m, M = (float)maxmin(m);
      if (M==m) ++M;
      const unsigned int w = width + 1, h = height + 1;
      CImg<floatT> points(w*h,3);
      cimg_forXY(*this,x,y) {
        const int yw = y*w, xpyw = x + yw, xpyww = xpyw + w;
        points(xpyw,0) = points(xpyw+1,0) = points(xpyww+1,0) = points(xpyww,0) = (float)x;
        points(xpyw,1) = points(xpyw+1,1) = points(xpyww+1,1) = points(xpyww,1) = (float)y;
        points(xpyw,2) = points(xpyw+1,2) = points(xpyww+1,2) = points(xpyww,2) = (float)elevation(x,y);
        primitives.insert(CImg<tf>::vector(xpyw,xpyw+1,xpyww+1,xpyww));
        const unsigned char
          r = (unsigned char)(((*this)(x,y,0) - m)*255/(M-m)),
          g = dim>1?(unsigned char)(((*this)(x,y,1) - m)*255/(M-m)):r,
          b = dim>2?(unsigned char)(((*this)(x,y,2) - m)*255/(M-m)):(dim>1?0:r);
        colors.insert(CImg<tc>::vector((tc)r,(tc)g,(tc)b));
      }
      return points;
    }

    // Inner routine used by the Marching square algorithm.
    template<typename t>
    static int _marching_squares_indice(const unsigned int edge, const CImg<t>& indices1, const CImg<t>& indices2,
                                        const unsigned int x, const unsigned int nx) {
      switch (edge) {
      case 0 : return (int)indices1(x,0);
      case 1 : return (int)indices1(nx,1);
      case 2 : return (int)indices2(x,0);
      case 3 : return (int)indices1(x,1);
      }
      return 0;
    }

    //! Polygonize an implicit 2D function by the marching squares algorithm.
    template<typename tf, typename tfunc>
    static CImg<floatT> marching_squares(CImgList<tf>& primitives, const tfunc& func, const float isovalue,
                                         const float x0, const float y0,
                                         const float x1, const float y1,
                                         const float resx, const float resy) {
      static unsigned int edges[16] = { 0x0, 0x9, 0x3, 0xa, 0x6, 0xf, 0x5, 0xc, 0xc, 0x5, 0xf, 0x6, 0xa, 0x3, 0x9, 0x0 };
      static int segments[16][4] = { { -1,-1,-1,-1 }, { 0,3,-1,-1 }, { 0,1,-1,-1 }, { 1,3,-1,-1 },
                                     { 1,2,-1,-1 },   { 0,1,2,3 },   { 0,2,-1,-1 }, { 2,3,-1,-1 },
                                     { 2,3,-1,-1 },   { 0,2,-1,-1},  { 0,3,1,2 },   { 1,2,-1,-1 },
                                     { 1,3,-1,-1 },   { 0,1,-1,-1},  { 0,3,-1,-1},  { -1,-1,-1,-1 } };
      const unsigned int
        nx = (unsigned int)((x1-x0+1)/resx), nxm1 = nx-1,
        ny = (unsigned int)((y1-y0+1)/resy), nym1 = ny-1;
      if (!nxm1 || !nym1) return CImg<floatT>();

      primitives.assign();
      CImgList<floatT> points;
      CImg<intT> indices1(nx,1,1,2,-1), indices2(nx,1,1,2);
      CImg<floatT> values1(nx), values2(nx);
      float X = 0, Y = 0, nX = 0, nY = 0;

      // Fill first line with values
      cimg_forX(values1,x) { values1(x) = (float)func(X,Y); X+=resx; }

      // Run the marching squares algorithm
      Y = y0; nY = Y + resy;
      for (unsigned int yi = 0, nyi = 1; yi<nym1; ++yi, ++nyi, Y=nY, nY+=resy) {
        X = x0; nX = X + resx;
        indices2.fill(-1);
        for (unsigned int xi = 0, nxi = 1; xi<nxm1; ++xi, ++nxi, X=nX, nX+=resx) {

          // Determine cube configuration
          const float
            val0 = values1(xi), val1 = values1(nxi),
            val2 = values2(nxi) = (float)func(nX,nY),
            val3 = values2(xi) = (float)func(X,nY);

          const unsigned int configuration = (val0<isovalue?1:0)  | (val1<isovalue?2:0)  | (val2<isovalue?4:0)  | (val3<isovalue?8:0),
            edge = edges[configuration];

          // Compute intersection points
          if (edge) {
            if ((edge&1) && indices1(xi,0)<0) {
              const float Xi = X + (isovalue-val0)*resx/(val1-val0);
              indices1(xi,0) = points.size;
              points.insert(CImg<floatT>::vector(Xi,Y));
            }
            if ((edge&2) && indices1(nxi,1)<0) {
              const float Yi = Y + (isovalue-val1)*resy/(val2-val1);
              indices1(nxi,1) = points.size;
              points.insert(CImg<floatT>::vector(nX,Yi));
            }
            if ((edge&4) && indices2(xi,0)<0) {
              const float Xi = X + (isovalue-val3)*resx/(val2-val3);
              indices2(xi,0) = points.size;
              points.insert(CImg<floatT>::vector(Xi,nY));
            }
            if ((edge&8) && indices1(xi,1)<0) {
              const float Yi = Y + (isovalue-val0)*resy/(val3-val0);
              indices1(xi,1) = points.size;
              points.insert(CImg<floatT>::vector(X,Yi));
            }

            // Create segments
            for (int *segment = segments[configuration]; *segment!=-1; ) {
              const unsigned int p0 = *(segment++), p1 = *(segment++);
              const tf
                i0 = (tf)(_marching_squares_indice(p0,indices1,indices2,xi,nxi)),
                i1 = (tf)(_marching_squares_indice(p1,indices1,indices2,xi,nxi));
              primitives.insert(CImg<tf>::vector(i0,i1));
            }
          }
        }
        values1.swap(values2);
        indices1.swap(indices2);
      }
      return points.get_append('x');
    }

    // Inner routine used by the Marching cube algorithm.
    template<typename t>
    static int _marching_cubes_indice(const unsigned int edge, const CImg<t>& indices1, const CImg<t>& indices2,
                                      const unsigned int x, const unsigned int y, const unsigned int nx, const unsigned int ny) {
      switch (edge) {
      case 0 : return indices1(x,y,0);
      case 1 : return indices1(nx,y,1);
      case 2 : return indices1(x,ny,0);
      case 3 : return indices1(x,y,1);
      case 4 : return indices2(x,y,0);
      case 5 : return indices2(nx,y,1);
      case 6 : return indices2(x,ny,0);
      case 7 : return indices2(x,y,1);
      case 8 : return indices1(x,y,2);
      case 9 : return indices1(nx,y,2);
      case 10 : return indices1(nx,ny,2);
      case 11 : return indices1(x,ny,2);
      }
      return 0;
    }

    //! Polygonize an implicit function
    // This function uses the Marching Cubes Tables published on the web page :
    // http://astronomy.swin.edu.au/~pbourke/modelling/polygonise/
    template<typename tf, typename tfunc>
    static CImg<floatT> marching_cubes(CImgList<tf>& primitives,
                                       const tfunc& func, const float isovalue,
                                       const float x0, const float y0, const float z0,
                                       const float x1, const float y1, const float z1,
                                       const float resx, const float resy, const float resz,
                                       const bool invert_faces=false) {

      static unsigned int edges[256] = {
        0x000, 0x109, 0x203, 0x30a, 0x406, 0x50f, 0x605, 0x70c, 0x80c, 0x905, 0xa0f, 0xb06, 0xc0a, 0xd03, 0xe09, 0xf00,
        0x190, 0x99 , 0x393, 0x29a, 0x596, 0x49f, 0x795, 0x69c, 0x99c, 0x895, 0xb9f, 0xa96, 0xd9a, 0xc93, 0xf99, 0xe90,
        0x230, 0x339, 0x33 , 0x13a, 0x636, 0x73f, 0x435, 0x53c, 0xa3c, 0xb35, 0x83f, 0x936, 0xe3a, 0xf33, 0xc39, 0xd30,
        0x3a0, 0x2a9, 0x1a3, 0xaa , 0x7a6, 0x6af, 0x5a5, 0x4ac, 0xbac, 0xaa5, 0x9af, 0x8a6, 0xfaa, 0xea3, 0xda9, 0xca0,
        0x460, 0x569, 0x663, 0x76a, 0x66 , 0x16f, 0x265, 0x36c, 0xc6c, 0xd65, 0xe6f, 0xf66, 0x86a, 0x963, 0xa69, 0xb60,
        0x5f0, 0x4f9, 0x7f3, 0x6fa, 0x1f6, 0xff , 0x3f5, 0x2fc, 0xdfc, 0xcf5, 0xfff, 0xef6, 0x9fa, 0x8f3, 0xbf9, 0xaf0,
        0x650, 0x759, 0x453, 0x55a, 0x256, 0x35f, 0x55 , 0x15c, 0xe5c, 0xf55, 0xc5f, 0xd56, 0xa5a, 0xb53, 0x859, 0x950,
        0x7c0, 0x6c9, 0x5c3, 0x4ca, 0x3c6, 0x2cf, 0x1c5, 0xcc , 0xfcc, 0xec5, 0xdcf, 0xcc6, 0xbca, 0xac3, 0x9c9, 0x8c0,
        0x8c0, 0x9c9, 0xac3, 0xbca, 0xcc6, 0xdcf, 0xec5, 0xfcc, 0xcc , 0x1c5, 0x2cf, 0x3c6, 0x4ca, 0x5c3, 0x6c9, 0x7c0,
        0x950, 0x859, 0xb53, 0xa5a, 0xd56, 0xc5f, 0xf55, 0xe5c, 0x15c, 0x55 , 0x35f, 0x256, 0x55a, 0x453, 0x759, 0x650,
        0xaf0, 0xbf9, 0x8f3, 0x9fa, 0xef6, 0xfff, 0xcf5, 0xdfc, 0x2fc, 0x3f5, 0xff , 0x1f6, 0x6fa, 0x7f3, 0x4f9, 0x5f0,
        0xb60, 0xa69, 0x963, 0x86a, 0xf66, 0xe6f, 0xd65, 0xc6c, 0x36c, 0x265, 0x16f, 0x66 , 0x76a, 0x663, 0x569, 0x460,
        0xca0, 0xda9, 0xea3, 0xfaa, 0x8a6, 0x9af, 0xaa5, 0xbac, 0x4ac, 0x5a5, 0x6af, 0x7a6, 0xaa , 0x1a3, 0x2a9, 0x3a0,
        0xd30, 0xc39, 0xf33, 0xe3a, 0x936, 0x83f, 0xb35, 0xa3c, 0x53c, 0x435, 0x73f, 0x636, 0x13a, 0x33 , 0x339, 0x230,
        0xe90, 0xf99, 0xc93, 0xd9a, 0xa96, 0xb9f, 0x895, 0x99c, 0x69c, 0x795, 0x49f, 0x596, 0x29a, 0x393, 0x99 , 0x190,
        0xf00, 0xe09, 0xd03, 0xc0a, 0xb06, 0xa0f, 0x905, 0x80c, 0x70c, 0x605, 0x50f, 0x406, 0x30a, 0x203, 0x109, 0x000 };

      static int triangles[256][16] =
        {{ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 0, 8, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
         { 0, 1, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 1, 8, 3, 9, 8, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
         { 1, 2, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 0, 8, 3, 1, 2, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
         { 9, 2, 10, 0, 2, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 2, 8, 3, 2, 10, 8, 10, 9, 8, -1, -1, -1, -1, -1, -1, -1 },
         { 3, 11, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 0, 11, 2, 8, 11, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
         { 1, 9, 0, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 1, 11, 2, 1, 9, 11, 9, 8, 11, -1, -1, -1, -1, -1, -1, -1 },
         { 3, 10, 1, 11, 10, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 0, 10, 1, 0, 8, 10, 8, 11, 10, -1, -1, -1, -1, -1, -1, -1 },
         { 3, 9, 0, 3, 11, 9, 11, 10, 9, -1, -1, -1, -1, -1, -1, -1 }, { 9, 8, 10, 10, 8, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
         { 4, 7, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 4, 3, 0, 7, 3, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
         { 0, 1, 9, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 4, 1, 9, 4, 7, 1, 7, 3, 1, -1, -1, -1, -1, -1, -1, -1 },
         { 1, 2, 10, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 3, 4, 7, 3, 0, 4, 1, 2, 10, -1, -1, -1, -1, -1, -1, -1 },
         { 9, 2, 10, 9, 0, 2, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1 }, { 2, 10, 9, 2, 9, 7, 2, 7, 3, 7, 9, 4, -1, -1, -1, -1 },
         { 8, 4, 7, 3, 11, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 11, 4, 7, 11, 2, 4, 2, 0, 4, -1, -1, -1, -1, -1, -1, -1 },
         { 9, 0, 1, 8, 4, 7, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1 }, { 4, 7, 11, 9, 4, 11, 9, 11, 2, 9, 2, 1, -1, -1, -1, -1 },
         { 3, 10, 1, 3, 11, 10, 7, 8, 4, -1, -1, -1, -1, -1, -1, -1 }, { 1, 11, 10, 1, 4, 11, 1, 0, 4, 7, 11, 4, -1, -1, -1, -1 },
         { 4, 7, 8, 9, 0, 11, 9, 11, 10, 11, 0, 3, -1, -1, -1, -1 }, { 4, 7, 11, 4, 11, 9, 9, 11, 10, -1, -1, -1, -1, -1, -1, -1 },
         { 9, 5, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 9, 5, 4, 0, 8, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
         { 0, 5, 4, 1, 5, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 8, 5, 4, 8, 3, 5, 3, 1, 5, -1, -1, -1, -1, -1, -1, -1 },
         { 1, 2, 10, 9, 5, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 3, 0, 8, 1, 2, 10, 4, 9, 5, -1, -1, -1, -1, -1, -1, -1 },
         { 5, 2, 10, 5, 4, 2, 4, 0, 2, -1, -1, -1, -1, -1, -1, -1 }, { 2, 10, 5, 3, 2, 5, 3, 5, 4, 3, 4, 8, -1, -1, -1, -1 },
         { 9, 5, 4, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 0, 11, 2, 0, 8, 11, 4, 9, 5, -1, -1, -1, -1, -1, -1, -1 },
         { 0, 5, 4, 0, 1, 5, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1 }, { 2, 1, 5, 2, 5, 8, 2, 8, 11, 4, 8, 5, -1, -1, -1, -1 },
         { 10, 3, 11, 10, 1, 3, 9, 5, 4, -1, -1, -1, -1, -1, -1, -1 }, { 4, 9, 5, 0, 8, 1, 8, 10, 1, 8, 11, 10, -1, -1, -1, -1 },
         { 5, 4, 0, 5, 0, 11, 5, 11, 10, 11, 0, 3, -1, -1, -1, -1 }, { 5, 4, 8, 5, 8, 10, 10, 8, 11, -1, -1, -1, -1, -1, -1, -1 },
         { 9, 7, 8, 5, 7, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 9, 3, 0, 9, 5, 3, 5, 7, 3, -1, -1, -1, -1, -1, -1, -1 },
         { 0, 7, 8, 0, 1, 7, 1, 5, 7, -1, -1, -1, -1, -1, -1, -1 }, { 1, 5, 3, 3, 5, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
         { 9, 7, 8, 9, 5, 7, 10, 1, 2, -1, -1, -1, -1, -1, -1, -1 }, { 10, 1, 2, 9, 5, 0, 5, 3, 0, 5, 7, 3, -1, -1, -1, -1 },
         { 8, 0, 2, 8, 2, 5, 8, 5, 7, 10, 5, 2, -1, -1, -1, -1 }, { 2, 10, 5, 2, 5, 3, 3, 5, 7, -1, -1, -1, -1, -1, -1, -1 },
         { 7, 9, 5, 7, 8, 9, 3, 11, 2, -1, -1, -1, -1, -1, -1, -1 }, { 9, 5, 7, 9, 7, 2, 9, 2, 0, 2, 7, 11, -1, -1, -1, -1 },
         { 2, 3, 11, 0, 1, 8, 1, 7, 8, 1, 5, 7, -1, -1, -1, -1 }, { 11, 2, 1, 11, 1, 7, 7, 1, 5, -1, -1, -1, -1, -1, -1, -1 },
         { 9, 5, 8, 8, 5, 7, 10, 1, 3, 10, 3, 11, -1, -1, -1, -1 }, { 5, 7, 0, 5, 0, 9, 7, 11, 0, 1, 0, 10, 11, 10, 0, -1 },
         { 11, 10, 0, 11, 0, 3, 10, 5, 0, 8, 0, 7, 5, 7, 0, -1 }, { 11, 10, 5, 7, 11, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
         { 10, 6, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 0, 8, 3, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
         { 9, 0, 1, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 1, 8, 3, 1, 9, 8, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1 },
         { 1, 6, 5, 2, 6, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 1, 6, 5, 1, 2, 6, 3, 0, 8, -1, -1, -1, -1, -1, -1, -1 },
         { 9, 6, 5, 9, 0, 6, 0, 2, 6, -1, -1, -1, -1, -1, -1, -1 }, { 5, 9, 8, 5, 8, 2, 5, 2, 6, 3, 2, 8, -1, -1, -1, -1 },
         { 2, 3, 11, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 11, 0, 8, 11, 2, 0, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1 },
         { 0, 1, 9, 2, 3, 11, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1 }, { 5, 10, 6, 1, 9, 2, 9, 11, 2, 9, 8, 11, -1, -1, -1, -1 },
         { 6, 3, 11, 6, 5, 3, 5, 1, 3, -1, -1, -1, -1, -1, -1, -1 }, { 0, 8, 11, 0, 11, 5, 0, 5, 1, 5, 11, 6, -1, -1, -1, -1 },
         { 3, 11, 6, 0, 3, 6, 0, 6, 5, 0, 5, 9, -1, -1, -1, -1 }, { 6, 5, 9, 6, 9, 11, 11, 9, 8, -1, -1, -1, -1, -1, -1, -1 },
         { 5, 10, 6, 4, 7, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 4, 3, 0, 4, 7, 3, 6, 5, 10, -1, -1, -1, -1, -1, -1, -1 },
         { 1, 9, 0, 5, 10, 6, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1 }, { 10, 6, 5, 1, 9, 7, 1, 7, 3, 7, 9, 4, -1, -1, -1, -1 },
         { 6, 1, 2, 6, 5, 1, 4, 7, 8, -1, -1, -1, -1, -1, -1, -1 }, { 1, 2, 5, 5, 2, 6, 3, 0, 4, 3, 4, 7, -1, -1, -1, -1 },
         { 8, 4, 7, 9, 0, 5, 0, 6, 5, 0, 2, 6, -1, -1, -1, -1 }, { 7, 3, 9, 7, 9, 4, 3, 2, 9, 5, 9, 6, 2, 6, 9, -1 },
         { 3, 11, 2, 7, 8, 4, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1 }, { 5, 10, 6, 4, 7, 2, 4, 2, 0, 2, 7, 11, -1, -1, -1, -1 },
         { 0, 1, 9, 4, 7, 8, 2, 3, 11, 5, 10, 6, -1, -1, -1, -1 }, { 9, 2, 1, 9, 11, 2, 9, 4, 11, 7, 11, 4, 5, 10, 6, -1 },
         { 8, 4, 7, 3, 11, 5, 3, 5, 1, 5, 11, 6, -1, -1, -1, -1 }, { 5, 1, 11, 5, 11, 6, 1, 0, 11, 7, 11, 4, 0, 4, 11, -1 },
         { 0, 5, 9, 0, 6, 5, 0, 3, 6, 11, 6, 3, 8, 4, 7, -1 }, { 6, 5, 9, 6, 9, 11, 4, 7, 9, 7, 11, 9, -1, -1, -1, -1 },
         { 10, 4, 9, 6, 4, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 4, 10, 6, 4, 9, 10, 0, 8, 3, -1, -1, -1, -1, -1, -1, -1 },
         { 10, 0, 1, 10, 6, 0, 6, 4, 0, -1, -1, -1, -1, -1, -1, -1 }, { 8, 3, 1, 8, 1, 6, 8, 6, 4, 6, 1, 10, -1, -1, -1, -1 },
         { 1, 4, 9, 1, 2, 4, 2, 6, 4, -1, -1, -1, -1, -1, -1, -1 }, { 3, 0, 8, 1, 2, 9, 2, 4, 9, 2, 6, 4, -1, -1, -1, -1 },
         { 0, 2, 4, 4, 2, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 8, 3, 2, 8, 2, 4, 4, 2, 6, -1, -1, -1, -1, -1, -1, -1 },
         { 10, 4, 9, 10, 6, 4, 11, 2, 3, -1, -1, -1, -1, -1, -1, -1 }, { 0, 8, 2, 2, 8, 11, 4, 9, 10, 4, 10, 6, -1, -1, -1, -1 },
         { 3, 11, 2, 0, 1, 6, 0, 6, 4, 6, 1, 10, -1, -1, -1, -1 }, { 6, 4, 1, 6, 1, 10, 4, 8, 1, 2, 1, 11, 8, 11, 1, -1 },
         { 9, 6, 4, 9, 3, 6, 9, 1, 3, 11, 6, 3, -1, -1, -1, -1 }, { 8, 11, 1, 8, 1, 0, 11, 6, 1, 9, 1, 4, 6, 4, 1, -1 },
         { 3, 11, 6, 3, 6, 0, 0, 6, 4, -1, -1, -1, -1, -1, -1, -1 }, { 6, 4, 8, 11, 6, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
         { 7, 10, 6, 7, 8, 10, 8, 9, 10, -1, -1, -1, -1, -1, -1, -1 }, { 0, 7, 3, 0, 10, 7, 0, 9, 10, 6, 7, 10, -1, -1, -1, -1 },
         { 10, 6, 7, 1, 10, 7, 1, 7, 8, 1, 8, 0, -1, -1, -1, -1 }, { 10, 6, 7, 10, 7, 1, 1, 7, 3, -1, -1, -1, -1, -1, -1, -1 },
         { 1, 2, 6, 1, 6, 8, 1, 8, 9, 8, 6, 7, -1, -1, -1, -1 }, { 2, 6, 9, 2, 9, 1, 6, 7, 9, 0, 9, 3, 7, 3, 9, -1 },
         { 7, 8, 0, 7, 0, 6, 6, 0, 2, -1, -1, -1, -1, -1, -1, -1 }, { 7, 3, 2, 6, 7, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
         { 2, 3, 11, 10, 6, 8, 10, 8, 9, 8, 6, 7, -1, -1, -1, -1 }, { 2, 0, 7, 2, 7, 11, 0, 9, 7, 6, 7, 10, 9, 10, 7, -1 },
         { 1, 8, 0, 1, 7, 8, 1, 10, 7, 6, 7, 10, 2, 3, 11, -1 }, { 11, 2, 1, 11, 1, 7, 10, 6, 1, 6, 7, 1, -1, -1, -1, -1 },
         { 8, 9, 6, 8, 6, 7, 9, 1, 6, 11, 6, 3, 1, 3, 6, -1 }, { 0, 9, 1, 11, 6, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
         { 7, 8, 0, 7, 0, 6, 3, 11, 0, 11, 6, 0, -1, -1, -1, -1 }, { 7, 11, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
         { 7, 6, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 3, 0, 8, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
         { 0, 1, 9, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 8, 1, 9, 8, 3, 1, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1 },
         { 10, 1, 2, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 1, 2, 10, 3, 0, 8, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1 },
         { 2, 9, 0, 2, 10, 9, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1 }, { 6, 11, 7, 2, 10, 3, 10, 8, 3, 10, 9, 8, -1, -1, -1, -1 },
         { 7, 2, 3, 6, 2, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 7, 0, 8, 7, 6, 0, 6, 2, 0, -1, -1, -1, -1, -1, -1, -1 },
         { 2, 7, 6, 2, 3, 7, 0, 1, 9, -1, -1, -1, -1, -1, -1, -1 }, { 1, 6, 2, 1, 8, 6, 1, 9, 8, 8, 7, 6, -1, -1, -1, -1 },
         { 10, 7, 6, 10, 1, 7, 1, 3, 7, -1, -1, -1, -1, -1, -1, -1 }, { 10, 7, 6, 1, 7, 10, 1, 8, 7, 1, 0, 8, -1, -1, -1, -1 },
         { 0, 3, 7, 0, 7, 10, 0, 10, 9, 6, 10, 7, -1, -1, -1, -1 }, { 7, 6, 10, 7, 10, 8, 8, 10, 9, -1, -1, -1, -1, -1, -1, -1 },
         { 6, 8, 4, 11, 8, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 3, 6, 11, 3, 0, 6, 0, 4, 6, -1, -1, -1, -1, -1, -1, -1 },
         { 8, 6, 11, 8, 4, 6, 9, 0, 1, -1, -1, -1, -1, -1, -1, -1 }, { 9, 4, 6, 9, 6, 3, 9, 3, 1, 11, 3, 6, -1, -1, -1, -1 },
         { 6, 8, 4, 6, 11, 8, 2, 10, 1, -1, -1, -1, -1, -1, -1, -1 }, { 1, 2, 10, 3, 0, 11, 0, 6, 11, 0, 4, 6, -1, -1, -1, -1 },
         { 4, 11, 8, 4, 6, 11, 0, 2, 9, 2, 10, 9, -1, -1, -1, -1 }, { 10, 9, 3, 10, 3, 2, 9, 4, 3, 11, 3, 6, 4, 6, 3, -1 },
         { 8, 2, 3, 8, 4, 2, 4, 6, 2, -1, -1, -1, -1, -1, -1, -1 }, { 0, 4, 2, 4, 6, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
         { 1, 9, 0, 2, 3, 4, 2, 4, 6, 4, 3, 8, -1, -1, -1, -1 }, { 1, 9, 4, 1, 4, 2, 2, 4, 6, -1, -1, -1, -1, -1, -1, -1 },
         { 8, 1, 3, 8, 6, 1, 8, 4, 6, 6, 10, 1, -1, -1, -1, -1 }, { 10, 1, 0, 10, 0, 6, 6, 0, 4, -1, -1, -1, -1, -1, -1, -1 },
         { 4, 6, 3, 4, 3, 8, 6, 10, 3, 0, 3, 9, 10, 9, 3, -1 }, { 10, 9, 4, 6, 10, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
         { 4, 9, 5, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 0, 8, 3, 4, 9, 5, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1 },
         { 5, 0, 1, 5, 4, 0, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1 }, { 11, 7, 6, 8, 3, 4, 3, 5, 4, 3, 1, 5, -1, -1, -1, -1 },
         { 9, 5, 4, 10, 1, 2, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1 }, { 6, 11, 7, 1, 2, 10, 0, 8, 3, 4, 9, 5, -1, -1, -1, -1 },
         { 7, 6, 11, 5, 4, 10, 4, 2, 10, 4, 0, 2, -1, -1, -1, -1 }, { 3, 4, 8, 3, 5, 4, 3, 2, 5, 10, 5, 2, 11, 7, 6, -1 },
         { 7, 2, 3, 7, 6, 2, 5, 4, 9, -1, -1, -1, -1, -1, -1, -1 }, { 9, 5, 4, 0, 8, 6, 0, 6, 2, 6, 8, 7, -1, -1, -1, -1 },
         { 3, 6, 2, 3, 7, 6, 1, 5, 0, 5, 4, 0, -1, -1, -1, -1 }, { 6, 2, 8, 6, 8, 7, 2, 1, 8, 4, 8, 5, 1, 5, 8, -1 },
         { 9, 5, 4, 10, 1, 6, 1, 7, 6, 1, 3, 7, -1, -1, -1, -1 }, { 1, 6, 10, 1, 7, 6, 1, 0, 7, 8, 7, 0, 9, 5, 4, -1 },
         { 4, 0, 10, 4, 10, 5, 0, 3, 10, 6, 10, 7, 3, 7, 10, -1 }, { 7, 6, 10, 7, 10, 8, 5, 4, 10, 4, 8, 10, -1, -1, -1, -1 },
         { 6, 9, 5, 6, 11, 9, 11, 8, 9, -1, -1, -1, -1, -1, -1, -1 }, { 3, 6, 11, 0, 6, 3, 0, 5, 6, 0, 9, 5, -1, -1, -1, -1 },
         { 0, 11, 8, 0, 5, 11, 0, 1, 5, 5, 6, 11, -1, -1, -1, -1 }, { 6, 11, 3, 6, 3, 5, 5, 3, 1, -1, -1, -1, -1, -1, -1, -1 },
         { 1, 2, 10, 9, 5, 11, 9, 11, 8, 11, 5, 6, -1, -1, -1, -1 }, { 0, 11, 3, 0, 6, 11, 0, 9, 6, 5, 6, 9, 1, 2, 10, -1 },
         { 11, 8, 5, 11, 5, 6, 8, 0, 5, 10, 5, 2, 0, 2, 5, -1 }, { 6, 11, 3, 6, 3, 5, 2, 10, 3, 10, 5, 3, -1, -1, -1, -1 },
         { 5, 8, 9, 5, 2, 8, 5, 6, 2, 3, 8, 2, -1, -1, -1, -1 }, { 9, 5, 6, 9, 6, 0, 0, 6, 2, -1, -1, -1, -1, -1, -1, -1 },
         { 1, 5, 8, 1, 8, 0, 5, 6, 8, 3, 8, 2, 6, 2, 8, -1 }, { 1, 5, 6, 2, 1, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
         { 1, 3, 6, 1, 6, 10, 3, 8, 6, 5, 6, 9, 8, 9, 6, -1 }, { 10, 1, 0, 10, 0, 6, 9, 5, 0, 5, 6, 0, -1, -1, -1, -1 },
         { 0, 3, 8, 5, 6, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 10, 5, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
         { 11, 5, 10, 7, 5, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 11, 5, 10, 11, 7, 5, 8, 3, 0, -1, -1, -1, -1, -1, -1, -1 },
         { 5, 11, 7, 5, 10, 11, 1, 9, 0, -1, -1, -1, -1, -1, -1, -1 }, { 10, 7, 5, 10, 11, 7, 9, 8, 1, 8, 3, 1, -1, -1, -1, -1 },
         { 11, 1, 2, 11, 7, 1, 7, 5, 1, -1, -1, -1, -1, -1, -1, -1 }, { 0, 8, 3, 1, 2, 7, 1, 7, 5, 7, 2, 11, -1, -1, -1, -1 },
         { 9, 7, 5, 9, 2, 7, 9, 0, 2, 2, 11, 7, -1, -1, -1, -1 }, { 7, 5, 2, 7, 2, 11, 5, 9, 2, 3, 2, 8, 9, 8, 2, -1 },
         { 2, 5, 10, 2, 3, 5, 3, 7, 5, -1, -1, -1, -1, -1, -1, -1 }, { 8, 2, 0, 8, 5, 2, 8, 7, 5, 10, 2, 5, -1, -1, -1, -1 },
         { 9, 0, 1, 5, 10, 3, 5, 3, 7, 3, 10, 2, -1, -1, -1, -1 }, { 9, 8, 2, 9, 2, 1, 8, 7, 2, 10, 2, 5, 7, 5, 2, -1 },
         { 1, 3, 5, 3, 7, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 0, 8, 7, 0, 7, 1, 1, 7, 5, -1, -1, -1, -1, -1, -1, -1 },
         { 9, 0, 3, 9, 3, 5, 5, 3, 7, -1, -1, -1, -1, -1, -1, -1 }, { 9, 8, 7, 5, 9, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
         { 5, 8, 4, 5, 10, 8, 10, 11, 8, -1, -1, -1, -1, -1, -1, -1 }, { 5, 0, 4, 5, 11, 0, 5, 10, 11, 11, 3, 0, -1, -1, -1, -1 },
         { 0, 1, 9, 8, 4, 10, 8, 10, 11, 10, 4, 5, -1, -1, -1, -1 }, { 10, 11, 4, 10, 4, 5, 11, 3, 4, 9, 4, 1, 3, 1, 4, -1 },
         { 2, 5, 1, 2, 8, 5, 2, 11, 8, 4, 5, 8, -1, -1, -1, -1 }, { 0, 4, 11, 0, 11, 3, 4, 5, 11, 2, 11, 1, 5, 1, 11, -1 },
         { 0, 2, 5, 0, 5, 9, 2, 11, 5, 4, 5, 8, 11, 8, 5, -1 }, { 9, 4, 5, 2, 11, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
         { 2, 5, 10, 3, 5, 2, 3, 4, 5, 3, 8, 4, -1, -1, -1, -1 }, { 5, 10, 2, 5, 2, 4, 4, 2, 0, -1, -1, -1, -1, -1, -1, -1 },
         { 3, 10, 2, 3, 5, 10, 3, 8, 5, 4, 5, 8, 0, 1, 9, -1 }, { 5, 10, 2, 5, 2, 4, 1, 9, 2, 9, 4, 2, -1, -1, -1, -1 },
         { 8, 4, 5, 8, 5, 3, 3, 5, 1, -1, -1, -1, -1, -1, -1, -1 }, { 0, 4, 5, 1, 0, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
         { 8, 4, 5, 8, 5, 3, 9, 0, 5, 0, 3, 5, -1, -1, -1, -1 }, { 9, 4, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
         { 4, 11, 7, 4, 9, 11, 9, 10, 11, -1, -1, -1, -1, -1, -1, -1 }, { 0, 8, 3, 4, 9, 7, 9, 11, 7, 9, 10, 11, -1, -1, -1, -1 },
         { 1, 10, 11, 1, 11, 4, 1, 4, 0, 7, 4, 11, -1, -1, -1, -1 }, { 3, 1, 4, 3, 4, 8, 1, 10, 4, 7, 4, 11, 10, 11, 4, -1 },
         { 4, 11, 7, 9, 11, 4, 9, 2, 11, 9, 1, 2, -1, -1, -1, -1 }, { 9, 7, 4, 9, 11, 7, 9, 1, 11, 2, 11, 1, 0, 8, 3, -1 },
         { 11, 7, 4, 11, 4, 2, 2, 4, 0, -1, -1, -1, -1, -1, -1, -1 }, { 11, 7, 4, 11, 4, 2, 8, 3, 4, 3, 2, 4, -1, -1, -1, -1 },
         { 2, 9, 10, 2, 7, 9, 2, 3, 7, 7, 4, 9, -1, -1, -1, -1 }, { 9, 10, 7, 9, 7, 4, 10, 2, 7, 8, 7, 0, 2, 0, 7, -1 },
         { 3, 7, 10, 3, 10, 2, 7, 4, 10, 1, 10, 0, 4, 0, 10, -1 }, { 1, 10, 2, 8, 7, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
         { 4, 9, 1, 4, 1, 7, 7, 1, 3, -1, -1, -1, -1, -1, -1, -1 }, { 4, 9, 1, 4, 1, 7, 0, 8, 1, 8, 7, 1, -1, -1, -1, -1 },
         { 4, 0, 3, 7, 4, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 4, 8, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
         { 9, 10, 8, 10, 11, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 3, 0, 9, 3, 9, 11, 11, 9, 10, -1, -1, -1, -1, -1, -1, -1 },
         { 0, 1, 10, 0, 10, 8, 8, 10, 11, -1, -1, -1, -1, -1, -1, -1 }, { 3, 1, 10, 11, 3, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
         { 1, 2, 11, 1, 11, 9, 9, 11, 8, -1, -1, -1, -1, -1, -1, -1 }, { 3, 0, 9, 3, 9, 11, 1, 2, 9, 2, 11, 9, -1, -1, -1, -1 },
         { 0, 2, 11, 8, 0, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 3, 2, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
         { 2, 3, 8, 2, 8, 10, 10, 8, 9, -1, -1, -1, -1, -1, -1, -1 }, { 9, 10, 2, 0, 9, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
         { 2, 3, 8, 2, 8, 10, 0, 1, 8, 1, 10, 8, -1, -1, -1, -1 }, { 1, 10, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
         { 1, 3, 8, 9, 1, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 0, 9, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
         { 0, 3, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }};

      const unsigned int
        nx = (unsigned int)((x1-x0+1)/resx), nxm1 = nx-1,
        ny = (unsigned int)((y1-y0+1)/resy), nym1 = ny-1,
        nz = (unsigned int)((z1-z0+1)/resz), nzm1 = nz-1;
      if (!nxm1 || !nym1 || !nzm1) return CImg<floatT>();

      primitives.assign();
      CImgList<floatT> points;
      CImg<intT> indices1(nx,ny,1,3,-1), indices2(indices1);
      CImg<floatT> values1(nx,ny), values2(nx,ny);
      float X = 0, Y = 0, Z = 0, nX = 0, nY = 0, nZ = 0;

      // Fill the first plane with function values
      Y = y0;
      cimg_forY(values1,y) {
        X = x0;
        cimg_forX(values1,x) { values1(x,y) = (float)func(X,Y,z0); X+=resx; }
        Y+=resy;
      }

      // Run Marching Cubes algorithm
      Z = z0; nZ = Z + resz;
      for (unsigned int zi = 0; zi<nzm1; ++zi, Z = nZ, nZ+=resz) {
        Y = y0; nY = Y + resy;
        indices2.fill(-1);
        for (unsigned int yi = 0, nyi = 1; yi<nym1; ++yi, ++nyi, Y = nY, nY+=resy) {
          X = x0; nX = X + resx;
          for (unsigned int xi = 0, nxi = 1; xi<nxm1; ++xi, ++nxi, X = nX, nX+=resx) {

            // Determine cube configuration
            const float
              val0 = values1(xi,yi), val1 = values1(nxi,yi), val2 = values1(nxi,nyi), val3 = values1(xi,nyi),
              val4 = values2(xi,yi) = (float)func(X,Y,nZ),
              val5 = values2(nxi,yi) = (float)func(nX,Y,nZ),
              val6 = values2(nxi,nyi) = (float)func(nX,nY,nZ),
              val7 = values2(xi,nyi) = (float)func(X,nY,nZ);

            const unsigned int configuration =
              (val0<isovalue?1:0)  | (val1<isovalue?2:0)  | (val2<isovalue?4:0)  | (val3<isovalue?8:0) |
              (val4<isovalue?16:0) | (val5<isovalue?32:0) | (val6<isovalue?64:0) | (val7<isovalue?128:0),
              edge = edges[configuration];

            // Compute intersection points
            if (edge) {
              if ((edge&1) && indices1(xi,yi,0)<0) {
                const float Xi = X + (isovalue-val0)*resx/(val1-val0);
                indices1(xi,yi,0) = points.size;
                points.insert(CImg<floatT>::vector(Xi,Y,Z));
              }
              if ((edge&2) && indices1(nxi,yi,1)<0) {
                const float Yi = Y + (isovalue-val1)*resy/(val2-val1);
                indices1(nxi,yi,1) = points.size;
                points.insert(CImg<floatT>::vector(nX,Yi,Z));
              }
              if ((edge&4) && indices1(xi,nyi,0)<0) {
                const float Xi = X + (isovalue-val3)*resx/(val2-val3);
                indices1(xi,nyi,0) = points.size;
                points.insert(CImg<floatT>::vector(Xi,nY,Z));
              }
              if ((edge&8) && indices1(xi,yi,1)<0) {
                const float Yi = Y + (isovalue-val0)*resy/(val3-val0);
                indices1(xi,yi,1) = points.size;
                points.insert(CImg<floatT>::vector(X,Yi,Z));
              }
              if ((edge&16) && indices2(xi,yi,0)<0) {
                const float Xi = X + (isovalue-val4)*resx/(val5-val4);
                indices2(xi,yi,0) = points.size;
                points.insert(CImg<floatT>::vector(Xi,Y,nZ));
              }
              if ((edge&32) && indices2(nxi,yi,1)<0) {
                const float Yi = Y + (isovalue-val5)*resy/(val6-val5);
                indices2(nxi,yi,1) = points.size;
                points.insert(CImg<floatT>::vector(nX,Yi,nZ));
              }
              if ((edge&64) && indices2(xi,nyi,0)<0) {
                const float Xi = X + (isovalue-val7)*resx/(val6-val7);
                indices2(xi,nyi,0) = points.size;
                points.insert(CImg<floatT>::vector(Xi,nY,nZ));
              }
              if ((edge&128) && indices2(xi,yi,1)<0)  {
                const float Yi = Y + (isovalue-val4)*resy/(val7-val4);
                indices2(xi,yi,1) = points.size;
                points.insert(CImg<floatT>::vector(X,Yi,nZ));
              }
              if ((edge&256) && indices1(xi,yi,2)<0) {
                const float Zi = Z+ (isovalue-val0)*resz/(val4-val0);
                indices1(xi,yi,2) = points.size;
                points.insert(CImg<floatT>::vector(X,Y,Zi));
              }
              if ((edge&512) && indices1(nxi,yi,2)<0)  {
                const float Zi = Z + (isovalue-val1)*resz/(val5-val1);
                indices1(nxi,yi,2) = points.size;
                points.insert(CImg<floatT>::vector(nX,Y,Zi));
              }
              if ((edge&1024) && indices1(nxi,nyi,2)<0) {
                const float Zi = Z + (isovalue-val2)*resz/(val6-val2);
                indices1(nxi,nyi,2) = points.size;
                points.insert(CImg<floatT>::vector(nX,nY,Zi));
              }
              if ((edge&2048) && indices1(xi,nyi,2)<0) {
                const float Zi = Z + (isovalue-val3)*resz/(val7-val3);
                indices1(xi,nyi,2) = points.size;
                points.insert(CImg<floatT>::vector(X,nY,Zi));
              }

              // Create triangles
              for (int *triangle = triangles[configuration]; *triangle!=-1; ) {
                const unsigned int p0 = *(triangle++), p1 = *(triangle++), p2 = *(triangle++);
                const tf
                  i0 = (tf)(_marching_cubes_indice(p0,indices1,indices2,xi,yi,nxi,nyi)),
                  i1 = (tf)(_marching_cubes_indice(p1,indices1,indices2,xi,yi,nxi,nyi)),
                  i2 = (tf)(_marching_cubes_indice(p2,indices1,indices2,xi,yi,nxi,nyi));
                if (invert_faces) primitives.insert(CImg<tf>::vector(i0,i1,i2));
                else primitives.insert(CImg<tf>::vector(i0,i2,i1));
              }
            }
          }
        }
        cimg::swap(values1,values2);
        cimg::swap(indices1,indices2);
      }
      return points.get_append('x');
    }

    struct _marching_squares_func {
      const CImg<T>& ref;
      explicit _marching_squares_func(const CImg<T>& pref):ref(pref) {}
      float operator()(const float x, const float y) const {
        return (float)ref((int)x,(int)y);
      }
    };

    struct _marching_cubes_func {
      const CImg<T>& ref;
      explicit _marching_cubes_func(const CImg<T>& pref):ref(pref) {}
      float operator()(const float x, const float y, const float z) const {
        return (float)ref((int)x,(int)y,(int)z);
      }
    };

    struct _marching_squares_func_float {
      const CImg<T>& ref;
      explicit _marching_squares_func_float(const CImg<T>& pref):ref(pref) {}
      float operator()(const float x, const float y) const {
        return (float)ref._linear_atXY(x,y);
      }
    };

    struct _marching_cubes_func_float {
      const CImg<T>& ref;
      explicit _marching_cubes_func_float(const CImg<T>& pref):ref(pref) {}
      float operator()(const float x, const float y, const float z) const {
        return (float)ref._linear_atXYZ(x,y,z);
      }
    };

    //! Compute a vectorization of an implicit function.
    template<typename tf>
    CImg<floatT> get_isovalue3d(CImgList<tf>& primitives, const float isovalue,
                                const float resx=1, const float resy=1, const float resz=1,
                                const bool invert_faces=false) const {
      primitives.assign();
      if (is_empty()) return *this;
      if (dim>1)
        throw CImgInstanceException("CImg<%s>::get_isovalue3d() : Instance image (%u,%u,%u,%u,%p) is not a scalar image.",
                                    pixel_type(),width,height,depth,dim,data);
      CImg<floatT> points;
      if (depth>1) {
        if (resx==1 && resy==1 && resz==1) {
          const _marching_cubes_func func(*this);
          points = marching_cubes(primitives,func,isovalue,0,0,0,dimx()-1.0f,dimy()-1.0f,dimz()-1.0f,resx,resy,resz,invert_faces);
        } else {
          const _marching_cubes_func_float func(*this);
          points = marching_cubes(primitives,func,isovalue,0,0,0,dimx()-1.0f,dimy()-1.0f,dimz()-1.0f,resx,resy,resz,invert_faces);
        }
      } else {
        if (resx==1 && resy==1) {
          const _marching_squares_func func(*this);
          points = marching_squares(primitives,func,isovalue,0,0,dimx()-1.0f,dimy()-1.0f,resx,resy);
        } else {
          const _marching_squares_func_float func(*this);
          points = marching_squares(primitives,func,isovalue,0,0,dimx()-1.0f,dimy()-1.0f,resx,resy);
        }
        if (points) points.resize(-100,3,1,1,0);
      }
      return points;
    }

    //! Translate a 3D object.
    CImg<T>& translate_object3d(const float tx, const float ty=0, const float tz=0) {
      get_shared_line(0)+=tx; get_shared_line(1)+=ty; get_shared_line(2)+=tz;
      return *this;
    }

    CImg<Tfloat> get_translate_object3d(const float tx, const float ty=0, const float tz=0) const {
      return CImg<Tfloat>(*this,false).translate_object3d(tx,ty,tz);
    }

    //! Translate a 3D object so that it becomes centered.
    CImg<T>& translate_object3d() {
      CImg<T> xcoords = get_shared_line(0), ycoords = get_shared_line(1), zcoords = get_shared_line(2);
      float xm, xM = (float)xcoords.maxmin(xm), ym, yM = (float)ycoords.maxmin(ym), zm, zM = (float)zcoords.maxmin(zm);
      xcoords-=(xm + xM)/2; ycoords-=(ym + yM)/2; zcoords-=(zm + zM)/2;
      return *this;
    }

    CImg<Tfloat> get_translate_object3d() const {
      return CImg<Tfloat>(*this,false).translate_object3d();
    }

    //! Resize a 3D object.
    CImg<T>& resize_object3d(const float sx, const float sy=-100, const float sz=-100) {
      CImg<T> xcoords = get_shared_line(0), ycoords = get_shared_line(1), zcoords = get_shared_line(2);
      float xm, xM = (float)xcoords.maxmin(xm), ym, yM = (float)ycoords.maxmin(ym), zm, zM = (float)zcoords.maxmin(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<Tfloat> get_resize_object3d(const float sx, const float sy=-100, const float sz=-100) const {
      return CImg<Tfloat>(*this,false).resize_object3d(sx,sy,sz);
    }

    // Resize a 3D object so that its max dimension if one.
    CImg<T> resize_object3d() const {
      CImg<T> xcoords = get_shared_line(0), ycoords = get_shared_line(1), zcoords = get_shared_line(2);
      float xm, xM = (float)xcoords.maxmin(xm), ym, yM = (float)ycoords.maxmin(ym), zm, zM = (float)zcoords.maxmin(zm);
      const float dx = xM - xm, dy = yM - ym, dz = zM - zm, dmax = cimg::max(dx,dy,dz);
      if (dmax>0) { xcoords/=dmax; ycoords/=dmax; zcoords/=dmax; }
      return *this;
    }

    CImg<Tfloat> get_resize_object3d() const {
      return CImg<Tfloat>(*this,false).resize_object3d();
    }

    //! Append a 3D object to another one.
    template<typename tf, typename tp, typename tff>
    CImg<T>& append_object3d(CImgList<tf>& primitives, const CImg<tp>& obj_points, const CImgList<tff>& obj_primitives) {
      const unsigned int P = width;
      append(obj_points,'x');
      const unsigned int N = primitives.size;
      primitives.insert(obj_primitives);
      for (unsigned int i = N; i<primitives.size; ++i) {
        CImg<tf> &p = primitives[i];
        if (p.size()!=5) p+=P;
        else { p[0]+=P; if (p[2]==0) p[1]+=P; }
      }
      return *this;
    }

    //@}
    //----------------------------
    //
    //! \name Color bases
    //@{
    //----------------------------

    //! Return a default indexed color palette with 256 (R,G,B) entries.
    /**
       The default color palette is used by %CImg when displaying images on 256 colors displays.
       It consists in the quantification of the (R,G,B) color space using 3:3:2 bits for color coding
       (i.e 8 levels for the Red and Green and 4 levels for the Blue).
       \return a 1x256x1x3 color image defining the palette entries.
    **/
    static CImg<Tuchar> default_LUT8() {
      static CImg<Tuchar> palette;
      if (!palette) {
        palette.assign(1,256,1,3);
        for (unsigned int index = 0, r = 16; r<256; r+=32)
          for (unsigned int g = 16; g<256; g+=32)
            for (unsigned int b = 32; b<256; b+=64) {
              palette(0,index,0) = (Tuchar)r;
              palette(0,index,1) = (Tuchar)g;
              palette(0,index++,2) = (Tuchar)b;
            }
      }
      return palette;
    }

    //! Return a rainbow color palette with 256 (R,G,B) entries.
    static CImg<Tuchar> rainbow_LUT8() {
      static CImg<Tuchar> palette;
      if (!palette) {
        CImg<Tint> tmp(1,256,1,3,1);
        tmp.get_shared_channel(0).sequence(0,359);
        palette = tmp.HSVtoRGB();
      }
      return palette;
    }

    //! Return a contrasted color palette with 256 (R,G,B) entries.
    static CImg<Tuchar> contrast_LUT8() {
      static const unsigned char pal[] = {
        217,62,88,75,1,237,240,12,56,160,165,116,1,1,204,2,15,248,148,185,133,141,46,246,222,116,16,5,207,226,
        17,114,247,1,214,53,238,0,95,55,233,235,109,0,17,54,33,0,90,30,3,0,94,27,19,0,68,212,166,130,0,15,7,119,
        238,2,246,198,0,3,16,10,13,2,25,28,12,6,2,99,18,141,30,4,3,140,12,4,30,233,7,10,0,136,35,160,168,184,20,
        233,0,1,242,83,90,56,180,44,41,0,6,19,207,5,31,214,4,35,153,180,75,21,76,16,202,218,22,17,2,136,71,74,
        81,251,244,148,222,17,0,234,24,0,200,16,239,15,225,102,230,186,58,230,110,12,0,7,129,249,22,241,37,219,
        1,3,254,210,3,212,113,131,197,162,123,252,90,96,209,60,0,17,0,180,249,12,112,165,43,27,229,77,40,195,12,
        87,1,210,148,47,80,5,9,1,137,2,40,57,205,244,40,8,252,98,0,40,43,206,31,187,0,180,1,69,70,227,131,108,0,
        223,94,228,35,248,243,4,16,0,34,24,2,9,35,73,91,12,199,51,1,249,12,103,131,20,224,2,70,32,
        233,1,165,3,8,154,246,233,196,5,0,6,183,227,247,195,208,36,0,0,226,160,210,198,69,153,210,1,23,8,192,2,4,
        137,1,0,52,2,249,241,129,0,0,234,7,238,71,7,32,15,157,157,252,158,2,250,6,13,30,11,162,0,199,21,11,27,224,
        4,157,20,181,111,187,218,3,0,11,158,230,196,34,223,22,248,135,254,210,157,219,0,117,239,3,255,4,227,5,247,
        11,4,3,188,111,11,105,195,2,0,14,1,21,219,192,0,183,191,113,241,1,12,17,248,0,48,7,19,1,254,212,0,239,246,
        0,23,0,250,165,194,194,17,3,253,0,24,6,0,141,167,221,24,212,2,235,243,0,0,205,1,251,133,204,28,4,6,1,10,
        141,21,74,12,236,254,228,19,1,0,214,1,186,13,13,6,13,16,27,209,6,216,11,207,251,59,32,9,155,23,19,235,143,
        116,6,213,6,75,159,23,6,0,228,4,10,245,249,1,7,44,234,4,102,174,0,19,239,103,16,15,18,8,214,22,4,47,244,
        255,8,0,251,173,1,212,252,250,251,252,6,0,29,29,222,233,246,5,149,0,182,180,13,151,0,203,183,0,35,149,0,
        235,246,254,78,9,17,203,73,11,195,0,3,5,44,0,0,237,5,106,6,130,16,214,20,168,247,168,4,207,11,5,1,232,251,
        129,210,116,231,217,223,214,27,45,38,4,177,186,249,7,215,172,16,214,27,249,230,236,2,34,216,217,0,175,30,
        243,225,244,182,20,212,2,226,21,255,20,0,2,13,62,13,191,14,76,64,20,121,4,118,0,216,1,147,0,2,210,1,215,
        95,210,236,225,184,46,0,248,24,11,1,9,141,250,243,9,221,233,160,11,147,2,55,8,23,12,253,9,0,54,0,231,6,3,
        141,8,2,246,9,180,5,11,8,227,8,43,110,242,1,130,5,97,36,10,6,219,86,133,11,108,6,1,5,244,67,19,28,0,174,
        154,16,127,149,252,188,196,196,228,244,9,249,0,0,0,37,170,32,250,0,73,255,23,3,224,234,38,195,198,0,255,87,
        33,221,174,31,3,0,189,228,6,153,14,144,14,108,197,0,9,206,245,254,3,16,253,178,248,0,95,125,8,0,3,168,21,
        23,168,19,50,240,244,185,0,1,144,10,168,31,82,1,13 };
      static const CImg<Tuchar> palette(pal,1,256,1,3,false);
      return palette;
    }

    //! Convert (R,G,B) color image to indexed color image.
    template<typename t>
    CImg<T>& RGBtoLUT(const CImg<t>& palette, const bool dithering=true, const bool indexing=false) {
      return get_RGBtoLUT(palette,dithering,indexing).transfer_to(*this);
    }

    template<typename t>
    CImg<t> get_RGBtoLUT(const CImg<t>& palette, const bool dithering=true, const bool indexing=false) const {
      if (is_empty()) return CImg<t>();
      if (dim!=3)
        throw CImgInstanceException("CImg<%s>::RGBtoLUT() : Input image dimension is dim=%u, "
                                    "should be a (R,G,B) image.",
                                    pixel_type(),dim);
      if (palette.data && palette.dim!=3)
        throw CImgArgumentException("CImg<%s>::RGBtoLUT() : Given palette dimension is dim=%u, "
                                    "should be a (R,G,B) palette",
                                    pixel_type(),palette.dim);
      CImg<t> res(width,height,depth,indexing?1:3);
      float *line1 = new float[3*width], *line2 = new float[3*width];
      t *pRd = res.ptr(0,0,0,0), *pGd = indexing?pRd:res.ptr(0,0,0,1), *pBd = indexing?pRd:res.ptr(0,0,0,2);
      cimg_forZ(*this,z) {
        const T *pRs = ptr(0,0,z,0), *pGs = ptr(0,0,z,1), *pBs = ptr(0,0,z,2);
        float *ptrd = line2; cimg_forX(*this,x) { *(ptrd++) = (float)*(pRs++); *(ptrd++) = (float)*(pGs++); *(ptrd++) = (float)*(pBs++); }
        cimg_forY(*this,y) {
          cimg::swap(line1,line2);
          if (y<dimy()-1) {
            const int ny = y + 1;
            const T *pRs = ptr(0,ny,z,0), *pGs = ptr(0,ny,z,1), *pBs = ptr(0,ny,z,2);
            float *ptrd = line2; cimg_forX(*this,x) { *(ptrd++) = (float)*(pRs++); *(ptrd++) = (float)*(pGs++); *(ptrd++) = (float)*(pBs++); }
          }
          float *ptr1 = line1, *ptr2 = line2;
          cimg_forX(*this,x) {
            float R = *(ptr1++), G = *(ptr1++), B = *(ptr1++);
            R = R<0?0:(R>255?255:R); G = G<0?0:(G>255?255:G); B = B<0?0:(B>255?255:B);
            t Rbest = 0, Gbest = 0, Bbest = 0;
            int best_index = 0;
            if (palette) { // find best match in given color palette
              const t *pRs = palette.ptr(0,0,0,0), *pGs = palette.ptr(0,0,0,1), *pBs = palette.ptr(0,0,0,2);
              const unsigned int Npal = palette.width*palette.height*palette.depth;
              float min = cimg::type<float>::max();
              for (unsigned int off = 0; off<Npal; ++off) {
                const t Rp = *(pRs++), Gp = *(pGs++), Bp = *(pBs++);
                const float error = cimg::sqr((float)Rp-(float)R) + cimg::sqr((float)Gp-(float)G) + cimg::sqr((float)Bp-(float)B);
                if (error<min) { min = error; best_index = off; Rbest = Rp; Gbest = Gp; Bbest = Bp; }
              }
            } else {
              Rbest = (t)((unsigned char)R&0xe0); Gbest = (t)((unsigned char)G&0xe0); Bbest = (t)((unsigned char)B&0xc0);
              best_index = (unsigned char)Rbest | ((unsigned char)Gbest>>3) | ((unsigned char)Bbest>>6);
            }
            if (indexing) *(pRd++) = (t)best_index; else { *(pRd++) = Rbest; *(pGd++) = Gbest; *(pBd++) = Bbest; }
            if (dithering) { // apply dithering to neighborhood pixels if needed
              const float dR = (float)(R-Rbest), dG = (float)(G-Gbest), dB = (float)(B-Bbest);
              if (x<dimx()-1) { *(ptr1++)+= dR*7/16; *(ptr1++)+= dG*7/16; *(ptr1++)+= dB*7/16; ptr1-=3; }
              if (y<dimy()-1) {
                *(ptr2++)+= dR*5/16; *(ptr2++)+= dG*5/16; *ptr2+= dB*5/16; ptr2-=2;
                if (x>0) { *(--ptr2)+= dB*3/16; *(--ptr2)+= dG*3/16; *(--ptr2)+= dR*3/16; ptr2+=3; }
                if (x<dimx()-1) { ptr2+=3; *(ptr2++)+= dR/16; *(ptr2++)+= dG/16; *ptr2+= dB/16; ptr2-=5; }
              }
            }
            ptr2+=3;
          }
        }
      }
      delete[] line1; delete[] line2;
      return res;
    }

    //! Convert color pixels from (R,G,B) to match the default palette.
    CImg<T>& RGBtoLUT(const bool dithering=true, const bool indexing=false) {
      return get_RGBtoLUT(dithering,indexing).transfer_to(*this);
    }

    CImg<Tuchar> get_RGBtoLUT(const bool dithering=true, const bool indexing=false) const {
      static const CImg<Tuchar> empty;
      return get_RGBtoLUT(empty,dithering,indexing);
    }

    //! Convert an indexed image to a (R,G,B) image using the specified color palette.
    CImg<T>& LUTtoRGB(const CImg<T>& palette) {
      return get_LUTtoRGB(palette).transfer_to(*this);
    }

    template<typename t>
    CImg<t> get_LUTtoRGB(const CImg<t>& palette) const {
      if (is_empty()) return CImg<t>();
      if (dim!=1)
        throw CImgInstanceException("CImg<%s>::LUTtoRGB() : Input image dimension is dim=%u, "
                                    "should be a LUT image",
                                    pixel_type(),dim);
      if (palette.data && palette.dim!=3)
        throw CImgArgumentException("CImg<%s>::LUTtoRGB() : Given palette dimension is dim=%u, "
                                    "should be a (R,G,B) palette",
                                    pixel_type(),palette.dim);
      const CImg<t> pal = palette.data?palette:CImg<t>(default_LUT8());
      CImg<t> res(width,height,depth,3);
      const t *pRs = pal.ptr(0,0,0,0), *pGs = pal.ptr(0,0,0,1), *pBs = pal.ptr(0,0,0,2);
      t *pRd = res.ptr(0,0,0,1), *pGd = pRd + width*height*depth, *pBd = pGd + width*height*depth;
      const unsigned int Npal = palette.width*palette.height*palette.depth;
      cimg_for(*this,ptr,T) {
        const unsigned int index = ((unsigned int)*ptr)%Npal;
        *(--pRd) = pRs[index]; *(--pGd) = pGs[index]; *(--pBd) = pBs[index];
      }
      return res;
    }

    //! Convert an indexed image (with the default palette) to a (R,G,B) image.
    CImg<T>& LUTtoRGB() {
      return get_LUTtoRGB().transfer_to(*this);
    }

    CImg<Tuchar> get_LUTtoRGB() const {
      static const CImg<Tuchar> empty;
      return get_LUTtoRGB(empty);
    }

    //! Convert color pixels from (R,G,B) to (H,S,V).
    CImg<T>& RGBtoHSV() {
      if (is_empty()) return *this;
      if (dim!=3)
        throw CImgInstanceException("CImg<%s>::RGBtoHSV() : Input image dimension is dim=%u, "
                                    "should be a (R,G,B) image.",
                                    pixel_type(),dim);
      T *p1 = ptr(0,0,0,0), *p2 = ptr(0,0,0,1), *p3 = ptr(0,0,0,2);
      for (unsigned long N = width*height*depth; N; --N) {
        const Tfloat
          R = (Tfloat)*p1,
          G = (Tfloat)*p2,
          B = (Tfloat)*p3,
          nR = (R<0?0:(R>255?255:R))/255,
          nG = (G<0?0:(G>255?255:G))/255,
          nB = (B<0?0:(B>255?255:B))/255,
          m = cimg::min(nR,nG,nB),
          M = cimg::max(nR,nG,nB);
        Tfloat H = 0, S = 0;
        if (M!=m) {
          const Tfloat
            f = (nR==m)?(nG-nB):((nG==m)?(nB-nR):(nR-nG)),
            i = (Tfloat)((nR==m)?3:((nG==m)?5:1));
          H = (i-f/(M-m));
          if (H>=6) H-=6;
          H*=60;
          S = (M-m)/M;
        }
        *(p1++) = (T)H;
        *(p2++) = (T)S;
        *(p3++) = (T)M;
      }
      return *this;
    }

    CImg<Tfloat> get_RGBtoHSV() const {
      return CImg<Tfloat>(*this,false).RGBtoHSV();
    }

    //! Convert color pixels from (H,S,V) to (R,G,B).
    CImg<T>& HSVtoRGB() {
    if (is_empty()) return *this;
    if (dim!=3)
      throw CImgInstanceException("CImg<%s>::HSVtoRGB() : Input image dimension is dim=%u, "
                                  "should be a (H,S,V) image",
                                  pixel_type(),dim);
    T *p1 = ptr(0,0,0,0), *p2 = ptr(0,0,0,1), *p3 = ptr(0,0,0,2);
    for (unsigned long N = width*height*depth; N; --N) {
      Tfloat
        H = (Tfloat)*p1,
        S = (Tfloat)*p2,
        V = (Tfloat)*p3,
        R = 0, G = 0, B = 0;
      if (H==0 && S==0) R = G = B = V;
      else {
        H/=60;
        const int i = (int)cimg_std::floor(H);
        const Tfloat
          f = (i&1)?(H-i):(1-H+i),
          m = V*(1-S),
          n = V*(1-S*f);
        switch (i) {
        case 6 :
        case 0 : R = V; G = n; B = m; break;
        case 1 : R = n; G = V; B = m; break;
        case 2 : R = m; G = V; B = n; break;
        case 3 : R = m; G = n; B = V; break;
        case 4 : R = n; G = m; B = V; break;
        case 5 : R = V; G = m; B = n; break;
        }
      }
      R*=255; G*=255; B*=255;
      *(p1++) = (T)(R<0?0:(R>255?255:R));
      *(p2++) = (T)(G<0?0:(G>255?255:G));
      *(p3++) = (T)(B<0?0:(B>255?255:B));
    }
    return *this;
    }

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

    //! Convert color pixels from (R,G,B) to (H,S,L).
    CImg<T>& RGBtoHSL() {
      if (is_empty()) return *this;
      if (dim!=3)
        throw CImgInstanceException("CImg<%s>::RGBtoHSL() : Input image dimension is dim=%u, "
                                    "should be a (R,G,B) image.",
                                    pixel_type(),dim);
      T *p1 = ptr(0,0,0,0), *p2 = ptr(0,0,0,1), *p3 = ptr(0,0,0,2);
      for (unsigned long N = width*height*depth; N; --N) {
        const Tfloat
          R = (Tfloat)*p1,
          G = (Tfloat)*p2,
          B = (Tfloat)*p3,
          nR = (R<0?0:(R>255?255:R))/255,
          nG = (G<0?0:(G>255?255:G))/255,
          nB = (B<0?0:(B>255?255:B))/255,
          m = cimg::min(nR,nG,nB),
          M = cimg::max(nR,nG,nB),
          L = (m+M)/2;
        Tfloat H = 0, S = 0;
        if (M==m) H = S = 0;
        else {
          const Tfloat
            f = (nR==m)?(nG-nB):((nG==m)?(nB-nR):(nR-nG)),
            i = (nR==m)?3.0f:((nG==m)?5.0f:1.0f);
          H = (i-f/(M-m));
          if (H>=6) H-=6;
          H*=60;
          S = (2*L<=1)?((M-m)/(M+m)):((M-m)/(2-M-m));
        }
        *(p1++) = (T)H;
        *(p2++) = (T)S;
        *(p3++) = (T)L;
      }
      return *this;
    }

    CImg<Tfloat> get_RGBtoHSL() const {
      return CImg< Tfloat>(*this,false).RGBtoHSL();
    }

    //! Convert color pixels from (H,S,L) to (R,G,B).
    CImg<T>& HSLtoRGB() {
      if (is_empty()) return *this;
      if (dim!=3)
        throw CImgInstanceException("CImg<%s>::HSLtoRGB() : Input image dimension is dim=%u, "
                                    "should be a (H,S,V) image",
                                    pixel_type(),dim);
      T *p1 = ptr(0,0,0,0), *p2 = ptr(0,0,0,1), *p3 = ptr(0,0,0,2);
      for (unsigned long N = width*height*depth; N; --N) {
        const Tfloat
          H = (Tfloat)*p1,
          S = (Tfloat)*p2,
          L = (Tfloat)*p3,
          q = 2*L<1?L*(1+S):(L+S-L*S),
          p = 2*L-q,
          h = H/360,
          tr = h + 1.0f/3,
          tg = h,
          tb = h - 1.0f/3,
          ntr = tr<0?tr+1:(tr>1?tr-1:tr),
          ntg = tg<0?tg+1:(tg>1?tg-1:tg),
          ntb = tb<0?tb+1:(tb>1?tb-1:tb),
          R = 255*(6*ntr<1?p+(q-p)*6*ntr:(2*ntr<1?q:(3*ntr<2?p+(q-p)*6*(2.0f/3-ntr):p))),
          G = 255*(6*ntg<1?p+(q-p)*6*ntg:(2*ntg<1?q:(3*ntg<2?p+(q-p)*6*(2.0f/3-ntg):p))),
          B = 255*(6*ntb<1?p+(q-p)*6*ntb:(2*ntb<1?q:(3*ntb<2?p+(q-p)*6*(2.0f/3-ntb):p)));
        *(p1++) = (T)(R<0?0:(R>255?255:R));
        *(p2++) = (T)(G<0?0:(G>255?255:G));
        *(p3++) = (T)(B<0?0:(B>255?255:B));
      }
      return *this;
    }

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

    //! Convert color pixels from (R,G,B) to (H,S,I).
    //! Reference: "Digital Image Processing, 2nd. edition", R. Gonzalez and R. Woods. Prentice Hall, 2002.
    CImg<T>& RGBtoHSI() {
      if (is_empty()) return *this;
      if (dim!=3)
        throw CImgInstanceException("CImg<%s>::RGBtoHSI() : Input image dimension is dim=%u, "
                                    "should be a (R,G,B) image.",
                                    pixel_type(),dim);
      T *p1 = ptr(0,0,0,0), *p2 = ptr(0,0,0,1), *p3 = ptr(0,0,0,2);
      for (unsigned long N = width*height*depth; N; --N) {
        const Tfloat
          R = (Tfloat)*p1,
          G = (Tfloat)*p2,
          B = (Tfloat)*p3,
          nR = (R<0?0:(R>255?255:R))/255,
          nG = (G<0?0:(G>255?255:G))/255,
          nB = (B<0?0:(B>255?255:B))/255,
          m = cimg::min(nR,nG,nB),
          theta = (Tfloat)(cimg_std::acos(0.5f*((nR-nG)+(nR-nB))/cimg_std::sqrt(cimg_std::pow(nR-nG,2)+(nR-nB)*(nG-nB)))*180/cimg::valuePI),
          sum = nR + nG + nB;
        Tfloat H = 0, S = 0, I = 0;
        if (theta>0) H = (nB<=nG)?theta:360-theta;
        if (sum>0) S = 1 - 3/sum*m;
        I = sum/3;
        *(p1++) = (T)H;
        *(p2++) = (T)S;
        *(p3++) = (T)I;
      }
      return *this;
    }

    CImg<Tfloat> get_RGBtoHSI() const {
      return CImg<Tfloat>(*this,false).RGBtoHSI();
    }

    //! Convert color pixels from (H,S,I) to (R,G,B).
    CImg<T>& HSItoRGB() {
      if (is_empty()) return *this;
      if (dim!=3)
        throw CImgInstanceException("CImg<%s>::HSItoRGB() : Input image dimension is dim=%u, "
                                    "should be a (H,S,I) image",
                                    pixel_type(),dim);
      T *p1 = ptr(0,0,0,0), *p2 = ptr(0,0,0,1), *p3 = ptr(0,0,0,2);
      for (unsigned long N = width*height*depth; N; --N) {
        Tfloat
          H = (Tfloat)*p1,
          S = (Tfloat)*p2,
          I = (Tfloat)*p3,
          a = I*(1-S),
          R = 0, G = 0, B = 0;
        if (H<120) {
          B = a;
          R = (Tfloat)(I*(1+S*cimg_std::cos(H*cimg::valuePI/180)/cimg_std::cos((60-H)*cimg::valuePI/180)));
          G = 3*I-(R+B);
        } else if (H<240) {
          H-=120;
          R = a;
          G = (Tfloat)(I*(1+S*cimg_std::cos(H*cimg::valuePI/180)/cimg_std::cos((60-H)*cimg::valuePI/180)));
          B = 3*I-(R+G);
        } else {
          H-=240;
          G = a;
          B = (Tfloat)(I*(1+S*cimg_std::cos(H*cimg::valuePI/180)/cimg_std::cos((60-H)*cimg::valuePI/180)));
          R = 3*I-(G+B);
        }
        R*=255; G*=255; B*=255;
        *(p1++) = (T)(R<0?0:(R>255?255:R));
        *(p2++) = (T)(G<0?0:(G>255?255:G));
        *(p3++) = (T)(B<0?0:(B>255?255:B));
      }
      return *this;
    }

    CImg<Tfloat> get_HSItoRGB() const {
      return CImg< Tuchar>(*this,false).HSItoRGB();
    }

    //! Convert color pixels from (R,G,B) to (Y,Cb,Cr)_8.
    CImg<T>& RGBtoYCbCr() {
      if (is_empty()) return *this;
      if (dim!=3)
        throw CImgInstanceException("CImg<%s>::RGBtoYCbCr() : Input image dimension is dim=%u, "
                                    "should be a (R,G,B) image (dim=3)",
                                    pixel_type(),dim);
      T *p1 = ptr(0,0,0,0), *p2 = ptr(0,0,0,1), *p3 = ptr(0,0,0,2);
      for (unsigned long N = width*height*depth; N; --N) {
        const Tfloat
          R = (Tfloat)*p1,
          G = (Tfloat)*p2,
          B = (Tfloat)*p3,
          Y = (66*R + 129*G + 25*B + 128)/256 + 16,
          Cb = (-38*R - 74*G + 112*B + 128)/256 + 128,
          Cr = (112*R - 94*G - 18*B + 128)/256 + 128;
        *(p1++) = (T)(Y<0?0:(Y>255?255:Y));
        *(p2++) = (T)(Cb<0?0:(Cb>255?255:Cb));
        *(p3++) = (T)(Cr<0?0:(Cr>255?255:Cr));
      }
      return *this;
    }

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

    //! Convert color pixels from (R,G,B) to (Y,Cb,Cr)_8.
    CImg<T>& YCbCrtoRGB() {
      if (is_empty()) return *this;
      if (dim!=3)
        throw CImgInstanceException("CImg<%s>::YCbCrtoRGB() : Input image dimension is dim=%u, "
                                    "should be a (Y,Cb,Cr)_8 image (dim=3)",
                                    pixel_type(),dim);
      T *p1 = ptr(0,0,0,0), *p2 = ptr(0,0,0,1), *p3 = ptr(0,0,0,2);
      for (unsigned long N = width*height*depth; N; --N) {
        const Tfloat
          Y = (Tfloat)*p1 - 16,
          Cb = (Tfloat)*p2 - 128,
          Cr = (Tfloat)*p3 - 128,
          R = (298*Y + 409*Cr + 128)/256,
          G = (298*Y - 100*Cb - 208*Cr + 128)/256,
          B = (298*Y + 516*Cb + 128)/256;
        *(p1++) = (T)(R<0?0:(R>255?255:R));
        *(p2++) = (T)(G<0?0:(G>255?255:G));
        *(p3++) = (T)(B<0?0:(B>255?255:B));
      }
      return *this;
    }

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

    //! Convert color pixels from (R,G,B) to (Y,U,V).
    CImg<T>& RGBtoYUV() {
      if (is_empty()) return *this;
      if (dim!=3)
        throw CImgInstanceException("CImg<%s>::RGBtoYUV() : Input image dimension is dim=%u, "
                                    "should be a (R,G,B) image (dim=3)",
                                    pixel_type(),dim);
      T *p1 = ptr(0,0,0,0), *p2 = ptr(0,0,0,1), *p3 = ptr(0,0,0,2);
      for (unsigned long N = width*height*depth; N; --N) {
        const Tfloat
          R = (Tfloat)*p1/255,
          G = (Tfloat)*p2/255,
          B = (Tfloat)*p3/255,
          Y = 0.299f*R + 0.587f*G + 0.114f*B;
        *(p1++) = (T)Y;
        *(p2++) = (T)(0.492f*(B-Y));
        *(p3++) = (T)(0.877*(R-Y));
      }
      return *this;
    }

    CImg<Tfloat> get_RGBtoYUV() const {
      return CImg<Tfloat>(*this,false).RGBtoYUV();
    }

    //! Convert color pixels from (Y,U,V) to (R,G,B).
    CImg<T>& YUVtoRGB() {
      if (is_empty()) return *this;
      if (dim!=3)
        throw CImgInstanceException("CImg<%s>::YUVtoRGB() : Input image dimension is dim=%u, "
                                    "should be a (Y,U,V) image (dim=3)",
                                    pixel_type(),dim);
      T *p1 = ptr(0,0,0,0), *p2 = ptr(0,0,0,1), *p3 = ptr(0,0,0,2);
      for (unsigned long N = width*height*depth; N; --N) {
        const Tfloat
          Y = (Tfloat)*p1,
          U = (Tfloat)*p2,
          V = (Tfloat)*p3,
          R = (Y + 1.140f*V)*255,
          G = (Y - 0.395f*U - 0.581f*V)*255,
          B = (Y + 2.032f*U)*255;
        *(p1++) = (T)(R<0?0:(R>255?255:R));
        *(p2++) = (T)(G<0?0:(G>255?255:G));
        *(p3++) = (T)(B<0?0:(B>255?255:B));
      }
      return *this;
    }

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

    //! Convert color pixels from (R,G,B) to (C,M,Y).
    CImg<T>& RGBtoCMY() {
      if (is_empty()) return *this;
      if (dim!=3)
        throw CImgInstanceException("CImg<%s>::RGBtoCMY() : Input image dimension is dim=%u, "
                                    "should be a (R,G,B) image (dim=3)",
                                    pixel_type(),dim);
      T *p1 = ptr(0,0,0,0), *p2 = ptr(0,0,0,1), *p3 = ptr(0,0,0,2);
      for (unsigned long N = width*height*depth; N; --N) {
        const Tfloat
          R = (Tfloat)*p1/255,
          G = (Tfloat)*p2/255,
          B = (Tfloat)*p3/255;
        *(p1++) = (T)(1 - R);
        *(p2++) = (T)(1 - G);
        *(p3++) = (T)(1 - B);
      }
      return *this;
    }

    CImg<Tfloat> get_RGBtoCMY() const {
      return CImg<Tfloat>(*this,false).RGBtoCMY();
    }

    //! Convert (C,M,Y) pixels of a color image into the (R,G,B) color space.
    CImg<T>& CMYtoRGB() {
      if (is_empty()) return *this;
      if (dim!=3)
        throw CImgInstanceException("CImg<%s>::CMYtoRGB() : Input image dimension is dim=%u, "
                                    "should be a (C,M,Y) image (dim=3)",
                                    pixel_type(),dim);
      T *p1 = ptr(0,0,0,0), *p2 = ptr(0,0,0,1), *p3 = ptr(0,0,0,2);
      for (unsigned long N = width*height*depth; N; --N) {
        const Tfloat
          C = (Tfloat)*p1,
          M = (Tfloat)*p2,
          Y = (Tfloat)*p3,
          R = 255*(1 - C),
          G = 255*(1 - M),
          B = 255*(1 - Y);
        *(p1++) = (T)(R<0?0:(R>255?255:R));
        *(p2++) = (T)(G<0?0:(G>255?255:G));
        *(p3++) = (T)(B<0?0:(B>255?255:B));
      }
      return *this;
    }

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

    //! Convert color pixels from (C,M,Y) to (C,M,Y,K).
    CImg<T>& CMYtoCMYK() {
      return get_CMYtoCMYK().transfer_to(*this);
    }

    CImg<Tfloat> get_CMYtoCMYK() const {
      if (is_empty()) return *this;
      if (dim!=3)
        throw CImgInstanceException("CImg<%s>::CMYtoCMYK() : Input image dimension is dim=%u, "
                                    "should be a (C,M,Y) image (dim=3)",
                                    pixel_type(),dim);
      CImg<Tfloat> res(width,height,depth,4);
      const T *ps1 = ptr(0,0,0,0), *ps2 = ptr(0,0,0,1), *ps3 = ptr(0,0,0,2);
      Tfloat *pd1 = res.ptr(0,0,0,0), *pd2 = res.ptr(0,0,0,1), *pd3 = res.ptr(0,0,0,2), *pd4 = res.ptr(0,0,0,3);
      for (unsigned long N = width*height*depth; N; --N) {
        Tfloat
          C = (Tfloat)*(ps1++),
          M = (Tfloat)*(ps2++),
          Y = (Tfloat)*(ps3++),
          K = cimg::min(C,M,Y);
        if (K==1) C = M = Y = 0;
        else { const Tfloat K1 = 1 - K; C = (C - K)/K1; M = (M - K)/K1; Y = (Y - K)/K1; }
        *(pd1++) = C;
        *(pd2++) = M;
        *(pd3++) = Y;
        *(pd4++) = K;
      }
      return res;
    }

    //! Convert (C,M,Y,K) pixels of a color image into the (C,M,Y) color space.
    CImg<T>& CMYKtoCMY() {
      return get_CMYKtoCMY().transfer_to(*this);
    }

    CImg<Tfloat> get_CMYKtoCMY() const {
      if (is_empty()) return *this;
      if (dim!=4)
        throw CImgInstanceException("CImg<%s>::CMYKtoCMY() : Input image dimension is dim=%u, "
                                    "should be a (C,M,Y,K) image (dim=4)",
                                    pixel_type(),dim);
      CImg<Tfloat> res(width,height,depth,3);
      const T *ps1 = ptr(0,0,0,0), *ps2 = ptr(0,0,0,1), *ps3 = ptr(0,0,0,2), *ps4 = ptr(0,0,0,3);
      Tfloat *pd1 = res.ptr(0,0,0,0), *pd2 = res.ptr(0,0,0,1), *pd3 = res.ptr(0,0,0,2);
      for (unsigned long N = width*height*depth; N; --N) {
        const Tfloat
          C = (Tfloat)*ps1,
          M = (Tfloat)*ps2,
          Y = (Tfloat)*ps3,
          K = (Tfloat)*ps4,
          K1 = 1 - K;
        *(pd1++) = C*K1 + K;
        *(pd2++) = M*K1 + K;
        *(pd3++) = Y*K1 + K;
      }
      return res;
    }

    //! Convert color pixels from (R,G,B) to (X,Y,Z)_709.
    CImg<T>& RGBtoXYZ() {
      if (is_empty()) return *this;
      if (dim!=3)
        throw CImgInstanceException("CImg<%s>::RGBtoXYZ() : Input image dimension is dim=%u, "
                                    "should be a (R,G,B) image (dim=3)",
                                    pixel_type(),dim);
      T *p1 = ptr(0,0,0,0), *p2 = ptr(0,0,0,1), *p3 = ptr(0,0,0,2);
      for (unsigned long N = width*height*depth; N; --N) {
        const Tfloat
          R = (Tfloat)*p1/255,
          G = (Tfloat)*p2/255,
          B = (Tfloat)*p3/255;
        *(p1++) = (T)(0.412453f*R + 0.357580f*G + 0.180423f*B);
        *(p2++) = (T)(0.212671f*R + 0.715160f*G + 0.072169f*B);
        *(p3++) = (T)(0.019334f*R + 0.119193f*G + 0.950227f*B);
      }
      return *this;
    }

    CImg<Tfloat> get_RGBtoXYZ() const {
      return CImg<Tfloat>(*this,false).RGBtoXYZ();
    }

    //! Convert (X,Y,Z)_709 pixels of a color image into the (R,G,B) color space.
    CImg<T>& XYZtoRGB() {
      if (is_empty()) return *this;
      if (dim!=3)
        throw CImgInstanceException("CImg<%s>::XYZtoRGB() : Input image dimension is dim=%u, "
                                    "should be a (X,Y,Z) image (dim=3)",
                                    pixel_type(),dim);
      T *p1 = ptr(0,0,0,0), *p2 = ptr(0,0,0,1), *p3 = ptr(0,0,0,2);
      for (unsigned long N = width*height*depth; N; --N) {
        const Tfloat
          X = (Tfloat)*p1*255,
          Y = (Tfloat)*p2*255,
          Z = (Tfloat)*p3*255,
          R = 3.240479f*X  - 1.537150f*Y - 0.498535f*Z,
          G = -0.969256f*X + 1.875992f*Y + 0.041556f*Z,
          B = 0.055648f*X  - 0.204043f*Y + 1.057311f*Z;
        *(p1++) = (T)(R<0?0:(R>255?255:R));
        *(p2++) = (T)(G<0?0:(G>255?255:G));
        *(p3++) = (T)(B<0?0:(B>255?255:B));
      }
      return *this;
    }

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

    //! Convert (X,Y,Z)_709 pixels of a color image into the (L*,a*,b*) color space.
    CImg<T>& XYZtoLab() {
#define _cimg_Labf(x) ((x)>=0.008856f?(cimg_std::pow(x,(Tfloat)1/3)):(7.787f*(x)+16.0f/116))
      if (is_empty()) return *this;
      if (dim!=3)
        throw CImgInstanceException("CImg<%s>::XYZtoLab() : Input image dimension is dim=%u, "
                                    "should be a (X,Y,Z) image (dim=3)",
                                    pixel_type(),dim);
      const Tfloat
        Xn = (Tfloat)(0.412453f + 0.357580f + 0.180423f),
        Yn = (Tfloat)(0.212671f + 0.715160f + 0.072169f),
        Zn = (Tfloat)(0.019334f + 0.119193f + 0.950227f);
      T *p1 = ptr(0,0,0,0), *p2 = ptr(0,0,0,1), *p3 = ptr(0,0,0,2);
      for (unsigned long N = width*height*depth; N; --N) {
        const Tfloat
          X = (Tfloat)*p1,
          Y = (Tfloat)*p2,
          Z = (Tfloat)*p3,
          XXn = X/Xn, YYn = Y/Yn, ZZn = Z/Zn,
          fX = (Tfloat)_cimg_Labf(XXn),
          fY = (Tfloat)_cimg_Labf(YYn),
          fZ = (Tfloat)_cimg_Labf(ZZn);
        *(p1++) = (T)(116*fY - 16);
        *(p2++) = (T)(500*(fX - fY));
        *(p3++) = (T)(200*(fY - fZ));
      }
      return *this;
    }

    CImg<Tfloat> get_XYZtoLab() const {
      return CImg<Tfloat>(*this,false).XYZtoLab();
    }

    //! Convert (L,a,b) pixels of a color image into the (X,Y,Z) color space.
    CImg<T>& LabtoXYZ() {
#define _cimg_Labfi(x) ((x)>=0.206893f?((x)*(x)*(x)):(((x)-16.0f/116)/7.787f))
      if (is_empty()) return *this;
      if (dim!=3)
        throw CImgInstanceException("CImg<%s>::LabtoXYZ() : Input image dimension is dim=%u, "
                                    "should be a (X,Y,Z) image (dim=3)",
                                    pixel_type(),dim);
      const Tfloat
        Xn = (Tfloat)(0.412453f + 0.357580f + 0.180423f),
        Yn = (Tfloat)(0.212671f + 0.715160f + 0.072169f),
        Zn = (Tfloat)(0.019334f + 0.119193f + 0.950227f);
      T *p1 = ptr(0,0,0,0), *p2 = ptr(0,0,0,1), *p3 = ptr(0,0,0,2);
      for (unsigned long N = width*height*depth; N; --N) {
        const Tfloat
          L = (Tfloat)*p1,
          a = (Tfloat)*p2,
          b = (Tfloat)*p3,
          cY = (L + 16)/116,
          Y = (Tfloat)(Yn*_cimg_Labfi(cY)),
          pY = (Tfloat)cimg_std::pow(Y/Yn,(Tfloat)1/3),
          cX = a/500 + pY,
          X = Xn*cX*cX*cX,
          cZ = pY - b/200,
          Z = Zn*cZ*cZ*cZ;
        *(p1++) = (T)(X);
        *(p2++) = (T)(Y);
        *(p3++) = (T)(Z);
      }
      return *this;
    }

    CImg<Tfloat> get_LabtoXYZ() const {
      return CImg<Tfloat>(*this,false).LabtoXYZ();
    }

    //! Convert (X,Y,Z)_709 pixels of a color image into the (x,y,Y) color space.
    CImg<T>& XYZtoxyY() {
      if (is_empty()) return *this;
      if (dim!=3)
        throw CImgInstanceException("CImg<%s>::XYZtoxyY() : Input image dimension is dim=%u, "
                                    "should be a (X,Y,Z) image (dim=3)",
                                    pixel_type(),dim);
      T *p1 = ptr(0,0,0,0), *p2 = ptr(0,0,0,1), *p3 = ptr(0,0,0,2);
      for (unsigned long N = width*height*depth; N; --N) {
        const Tfloat
          X = (Tfloat)*p1,
          Y = (Tfloat)*p2,
          Z = (Tfloat)*p3,
          sum = (X+Y+Z),
          nsum = sum>0?sum:1;
        *(p1++) = (T)(X/nsum);
        *(p2++) = (T)(Y/nsum);
        *(p3++) = (T)Y;
      }
      return *this;
    }

    CImg<Tfloat> get_XYZtoxyY() const {
      return CImg<Tfloat>(*this,false).XYZtoxyY();
    }

    //! Convert (x,y,Y) pixels of a color image into the (X,Y,Z)_709 color space.
    CImg<T>& xyYtoXYZ() {
      if (is_empty()) return *this;
      if (dim!=3)
        throw CImgInstanceException("CImg<%s>::xyYtoXYZ() : Input image dimension is dim=%u, "
                                    "should be a (x,y,Y) image (dim=3)",
                                    pixel_type(),dim);
      T *p1 = ptr(0,0,0,0), *p2 = ptr(0,0,0,1), *p3 = ptr(0,0,0,2);
      for (unsigned long N = width*height*depth; N; --N) {
        const Tfloat
         px = (Tfloat)*p1,
         py = (Tfloat)*p2,
         Y = (Tfloat)*p3,
         ny = py>0?py:1;
        *(p1++) = (T)(px*Y/ny);
        *(p2++) = (T)Y;
        *(p3++) = (T)((1-px-py)*Y/ny);
      }
      return *this;
    }

    CImg<Tfloat> get_xyYtoXYZ() const {
      return CImg<Tfloat>(*this,false).xyYtoXYZ();
    }

    //! Convert a (R,G,B) image to a (L,a,b) one.
    CImg<T>& RGBtoLab() {
      return RGBtoXYZ().XYZtoLab();
    }

    CImg<Tfloat> get_RGBtoLab() const {
      return CImg<Tfloat>(*this,false).RGBtoLab();
    }

    //! Convert a (L,a,b) image to a (R,G,B) one.
    CImg<T>& LabtoRGB() {
      return LabtoXYZ().XYZtoRGB();
    }

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

    //! Convert a (R,G,B) image to a (x,y,Y) one.
    CImg<T>& RGBtoxyY() {
      return RGBtoXYZ().XYZtoxyY();
    }

    CImg<Tfloat> get_RGBtoxyY() const {
      return CImg<Tfloat>(*this,false).RGBtoxyY();
    }

    //! Convert a (x,y,Y) image to a (R,G,B) one.
    CImg<T>& xyYtoRGB() {
      return xyYtoXYZ().XYZtoRGB();
    }

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

    //! Convert a (R,G,B) image to a (C,M,Y,K) one.
    CImg<T>& RGBtoCMYK() {
      return RGBtoCMY().CMYtoCMYK();
    }

    CImg<Tfloat> get_RGBtoCMYK() const {
      return CImg<Tfloat>(*this,false).RGBtoCMYK();
    }

    //! Convert a (C,M,Y,K) image to a (R,G,B) one.
    CImg<T>& CMYKtoRGB() {
      return CMYKtoCMY().CMYtoRGB();
    }

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

    //! Convert a (R,G,B) image to a Bayer-coded representation.
    /**
       \note First (upper-left) pixel if the red component of the pixel color.
    **/
    CImg<T>& RGBtoBayer() {
      return get_RGBtoBayer().transfer_to(*this);
    }

    CImg<T> get_RGBtoBayer() const {
      if (is_empty()) return *this;
      if (dim!=3)
        throw CImgInstanceException("CImg<%s>::RGBtoBayer() : Input image dimension is dim=%u, "
                                    "should be a (R,G,B) image (dim=3)",
                                    pixel_type(),dim);
      CImg<T> res(width,height,depth,1);
      const T *pR = ptr(0,0,0,0), *pG = ptr(0,0,0,1), *pB = ptr(0,0,0,2);
      T *ptrd = res.data;
      cimg_forXYZ(*this,x,y,z) {
        if (y%2) {
          if (x%2) *(ptrd++) = *pB;
          else *(ptrd++) = *pG;
        } else {
          if (x%2) *(ptrd++) = *pG;
          else *(ptrd++) = *pR;
        }
        ++pR; ++pG; ++pB;
      }
      return res;
    }

    //! Convert a Bayer-coded image to a (R,G,B) color image.
    CImg<T>& BayertoRGB(const unsigned int interpolation_type=3) {
      return get_BayertoRGB(interpolation_type).transfer_to(*this);
    }

    CImg<Tuchar> get_BayertoRGB(const unsigned int interpolation_type=3) const {
      if (is_empty()) return *this;
      if (dim!=1)
        throw CImgInstanceException("CImg<%s>::BayertoRGB() : Input image dimension is dim=%u, "
                                    "should be a Bayer image (dim=1)",
                                    pixel_type(),dim);
      CImg<Tuchar> res(width,height,depth,3);
      CImg_3x3(I,T);
      Tuchar *pR = res.ptr(0,0,0,0), *pG = res.ptr(0,0,0,1), *pB = res.ptr(0,0,0,2);
      switch (interpolation_type) {
      case 3 : { // Edge-directed
        CImg_3x3(R,T);
        CImg_3x3(G,T);
        CImg_3x3(B,T);
        cimg_forXYZ(*this,x,y,z) {
          const int _p1x = x?x-1:1, _p1y = y?y-1:1, _n1x = x<dimx()-1?x+1:x-1, _n1y = y<dimy()-1?y+1:y-1;
          cimg_get3x3(*this,x,y,z,0,I);
          if (y%2) {
            if (x%2) {
              const Tfloat alpha = cimg::sqr((Tfloat)Inc - Ipc), beta = cimg::sqr((Tfloat)Icn - Icp), cx = 1/(1+alpha), cy = 1/(1+beta);
              *pG = (Tuchar)((cx*(Inc+Ipc) + cy*(Icn+Icp))/(2*(cx+cy)));
            } else *pG = (Tuchar)Icc;
          } else {
            if (x%2) *pG = (Tuchar)Icc;
            else {
              const Tfloat alpha = cimg::sqr((Tfloat)Inc - Ipc), beta = cimg::sqr((Tfloat)Icn - Icp), cx = 1/(1+alpha), cy = 1/(1+beta);
              *pG = (Tuchar)((cx*(Inc+Ipc) + cy*(Icn+Icp))/(2*(cx+cy)));
            }
          }
          ++pG;
        }
        cimg_forXYZ(*this,x,y,z) {
          const int _p1x = x?x-1:1, _p1y = y?y-1:1, _n1x = x<dimx()-1?x+1:x-1, _n1y = y<dimy()-1?y+1:y-1;
          cimg_get3x3(*this,x,y,z,0,I);
          cimg_get3x3(res,x,y,z,1,G);
          if (y%2) {
            if (x%2) *pB = (Tuchar)Icc;
            else { *pR = (Tuchar)((Icn+Icp)/2); *pB = (Tuchar)((Inc+Ipc)/2); }
          } else {
            if (x%2) { *pR = (Tuchar)((Inc+Ipc)/2); *pB = (Tuchar)((Icn+Icp)/2); }
            else *pR = (Tuchar)Icc;
          }
          ++pR; ++pB;
        }
        pR = res.ptr(0,0,0,0);
        pG = res.ptr(0,0,0,1);
        pB = res.ptr(0,0,0,2);
        cimg_forXYZ(*this,x,y,z) {
          const int _p1x = x?x-1:1, _p1y = y?y-1:1, _n1x = x<dimx()-1?x+1:x-1, _n1y = y<dimy()-1?y+1:y-1;
          cimg_get3x3(res,x,y,z,0,R);
          cimg_get3x3(res,x,y,z,1,G);
          cimg_get3x3(res,x,y,z,2,B);
          if (y%2) {
            if (x%2) {
              const float alpha = (float)cimg::sqr(Rnc-Rpc), beta = (float)cimg::sqr(Rcn-Rcp), cx = 1/(1+alpha), cy = 1/(1+beta);
              *pR = (Tuchar)((cx*(Rnc+Rpc) + cy*(Rcn+Rcp))/(2*(cx+cy)));
            }
          } else {
            if (!(x%2)) {
              const float alpha = (float)cimg::sqr(Bnc-Bpc), beta = (float)cimg::sqr(Bcn-Bcp), cx = 1/(1+alpha), cy = 1/(1+beta);
              *pB = (Tuchar)((cx*(Bnc+Bpc) + cy*(Bcn+Bcp))/(2*(cx+cy)));
            }
          }
          ++pR; ++pG; ++pB;
        }
      } break;
      case 2 : { // Linear interpolation
        cimg_forXYZ(*this,x,y,z) {
          const int _p1x = x?x-1:1, _p1y = y?y-1:1, _n1x = x<dimx()-1?x+1:x-1, _n1y = y<dimy()-1?y+1:y-1;
          cimg_get3x3(*this,x,y,z,0,I);
          if (y%2) {
            if (x%2) { *pR = (Tuchar)((Ipp+Inn+Ipn+Inp)/4); *pG = (Tuchar)((Inc+Ipc+Icn+Icp)/4); *pB = (Tuchar)Icc; }
            else { *pR = (Tuchar)((Icp+Icn)/2); *pG = (Tuchar)Icc; *pB = (Tuchar)((Inc+Ipc)/2); }
          } else {
            if (x%2) { *pR = (Tuchar)((Ipc+Inc)/2); *pG = (Tuchar)Icc; *pB = (Tuchar)((Icn+Icp)/2); }
            else { *pR = (Tuchar)Icc; *pG = (Tuchar)((Inc+Ipc+Icn+Icp)/4); *pB = (Tuchar)((Ipp+Inn+Ipn+Inp)/4); }
          }
          ++pR; ++pG; ++pB;
        }
      } break;
      case 1 : { // Nearest neighbor interpolation
        cimg_forXYZ(*this,x,y,z) {
          const int _p1x = x?x-1:1, _p1y = y?y-1:1, _n1x = x<dimx()-1?x+1:x-1, _n1y = y<dimy()-1?y+1:y-1;
          cimg_get3x3(*this,x,y,z,0,I);
          if (y%2) {
            if (x%2) { *pR = (Tuchar)cimg::min(Ipp,Inn,Ipn,Inp); *pG = (Tuchar)cimg::min(Inc,Ipc,Icn,Icp); *pB = (Tuchar)Icc; }
            else { *pR = (Tuchar)cimg::min(Icn,Icp); *pG = (Tuchar)Icc; *pB = (Tuchar)cimg::min(Inc,Ipc); }
          } else {
            if (x%2) { *pR = (Tuchar)cimg::min(Inc,Ipc); *pG = (Tuchar)Icc; *pB = (Tuchar)cimg::min(Icn,Icp); }
            else { *pR = (Tuchar)Icc; *pG = (Tuchar)cimg::min(Inc,Ipc,Icn,Icp); *pB = (Tuchar)cimg::min(Ipp,Inn,Ipn,Inp); }
          }
          ++pR; ++pG; ++pB;
        }
      } break;
      default : { // 0-filling interpolation
        const T *ptrs = data;
        res.fill(0);
        cimg_forXYZ(*this,x,y,z) {
          const T val = *(ptrs++);
          if (y%2) { if (x%2) *pB = val; else *pG = val; } else { if (x%2) *pG = val; else *pR = val; }
          ++pR; ++pG; ++pB;
        }
      }
      }
      return res;
    }

    //@}
    //-------------------
    //
    //! \name Drawing
    //@{
    //-------------------

    // The following _draw_scanline() routines are *non user-friendly functions*, used only for internal purpose.
    // Pre-requisites : x0<x1, y-coordinate is valid, col is valid.
    template<typename tc>
    CImg<T>& _draw_scanline(const int x0, const int x1, const int y,
                            const tc *const color, const float opacity=1,
                            const float brightness=1, const bool init=false) {
      static const T maxval = (T)cimg::min(cimg::type<T>::max(),cimg::type<tc>::max());
      static float nopacity = 0, copacity = 0;
      static unsigned int whz = 0;
      static const tc *col = 0;
      if (init) {
        nopacity = cimg::abs(opacity);
        copacity = 1 - cimg::max(opacity,0);
        whz = width*height*depth;
      } else {
        const int nx0 = x0>0?x0:0, nx1 = x1<dimx()?x1:dimx()-1, dx = nx1 - nx0;
        if (dx>=0) {
          col = color;
          const unsigned int off = whz-dx-1;
          T *ptrd = ptr(nx0,y);
          if (opacity>=1) { // ** Opaque drawing **
            if (brightness==1) { // Brightness==1
              if (sizeof(T)!=1) cimg_forV(*this,k) {
                const T val = (T)*(col++);
                for (int x = dx; x>=0; --x) *(ptrd++) = val;
                ptrd+=off;
              } else cimg_forV(*this,k) {
                const T val = (T)*(col++);
                cimg_std::memset(ptrd,(int)val,dx+1);
                ptrd+=whz;
              }
            } else if (brightness<1) { // Brightness<1
              if (sizeof(T)!=1) cimg_forV(*this,k) {
                const T val = (T)(*(col++)*brightness);
                for (int x = dx; x>=0; --x) *(ptrd++) = val;
                ptrd+=off;
              } else cimg_forV(*this,k) {
                const T val = (T)(*(col++)*brightness);
                cimg_std::memset(ptrd,(int)val,dx+1);
                ptrd+=whz;
              }
            } else { // Brightness>1
              if (sizeof(T)!=1) cimg_forV(*this,k) {
                const T val = (T)((2-brightness)**(col++) + (brightness-1)*maxval);
                for (int x = dx; x>=0; --x) *(ptrd++) = val;
                ptrd+=off;
              } else cimg_forV(*this,k) {
                const T val = (T)((2-brightness)**(col++) + (brightness-1)*maxval);
                cimg_std::memset(ptrd,(int)val,dx+1);
                ptrd+=whz;
              }
            }
          } else { // ** Transparent drawing **
            if (brightness==1) { // Brightness==1
              cimg_forV(*this,k) {
                const T val = (T)*(col++);
                for (int x = dx; x>=0; --x) { *ptrd = (T)(val*nopacity + *ptrd*copacity); ++ptrd; }
                ptrd+=off;
              }
            } else if (brightness<=1) { // Brightness<1
              cimg_forV(*this,k) {
                const T val = (T)(*(col++)*brightness);
                for (int x = dx; x>=0; --x) { *ptrd = (T)(val*nopacity + *ptrd*copacity); ++ptrd; }
                ptrd+=off;
              }
            } else { // Brightness>1
              cimg_forV(*this,k) {
                const T val = (T)((2-brightness)**(col++) + (brightness-1)*maxval);
                for (int x = dx; x>=0; --x) { *ptrd = (T)(val*nopacity + *ptrd*copacity); ++ptrd; }
                ptrd+=off;
              }
            }
          }
        }
      }
      return *this;
    }

    template<typename tc>
    CImg<T>& _draw_scanline(const tc *const color, const float opacity=1) {
      return _draw_scanline(0,0,0,color,opacity,0,true);
    }

    //! Draw a 2D colored point (pixel).
    /**
       \param x0 X-coordinate of the point.
       \param y0 Y-coordinate of the point.
       \param color Pointer to \c dimv() consecutive values, defining the color values.
       \param opacity Drawing opacity (optional).
       \note
       - Clipping is supported.
       - To set pixel values without clipping needs, you should use the faster CImg::operator()() function.
       \par Example:
       \code
       CImg<unsigned char> img(100,100,1,3,0);
       const unsigned char color[] = { 255,128,64 };
       img.draw_point(50,50,color);
       \endcode
    **/
    template<typename tc>
    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);
    }

    //! Draw a 2D colored point (pixel).
    template<typename tc>
    CImg<T>& draw_point(const int x0, const int y0,
                        const CImg<tc>& color, const float opacity=1) {
      return draw_point(x0,y0,color.data,opacity);
    }

    //! Draw a 3D colored point (voxel).
    template<typename tc>
    CImg<T>& draw_point(const int x0, const int y0, const int z0,
                        const tc *const color, const float opacity=1) {
      if (is_empty()) return *this;
      if (!color)
        throw CImgArgumentException("CImg<%s>::draw_point() : Specified color is (null)",
                                    pixel_type());
      if (x0>=0 && y0>=0 && z0>=0 && x0<dimx() && y0<dimy() && z0<dimz()) {
        const unsigned int whz = width*height*depth;
        const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
        T *ptrd = ptr(x0,y0,z0,0);
        const tc *col = color;
        if (opacity>=1) cimg_forV(*this,k) { *ptrd = (T)*(col++); ptrd+=whz; }
        else cimg_forV(*this,k) { *ptrd = (T)(*(col++)*nopacity + *ptrd*copacity); ptrd+=whz; }
      }
      return *this;
    }

    //! Draw a 3D colored point (voxel).
    template<typename tc>
    CImg<T>& draw_point(const int x0, const int y0, const int z0,
                        const CImg<tc>& color, const float opacity=1) {
      return draw_point(x0,y0,z0,color.data,opacity);
    }

    // Draw a cloud of colored point (internal).
    template<typename t, typename tc>
    CImg<T>& _draw_point(const t& points, const unsigned int W, const unsigned int H,
                         const tc *const color, const float opacity) {
      if (is_empty() || !points || !W) return *this;
      switch (H) {
      case 0 : case 1 :
        throw CImgArgumentException("CImg<%s>::draw_point() : Given list of points is not valid.",
                                    pixel_type());
      case 2 : {
        for (unsigned int i = 0; i<W; ++i) {
          const int x = (int)points(i,0), y = (int)points(i,1);
          draw_point(x,y,color,opacity);
        }
      } break;
      default : {
        for (unsigned int i = 0; i<W; ++i) {
          const int x = (int)points(i,0), y = (int)points(i,1), z = (int)points(i,2);
          draw_point(x,y,z,color,opacity);
        }
      }
      }
      return *this;
    }

    //! Draw a cloud of colored points.
    /**
       \param points Coordinates of vertices, stored as a list of vectors.
       \param color Pointer to \c dimv() consecutive values of type \c T, defining the drawing color.
       \param opacity Drawing opacity (optional).
       \note
       - This function uses several call to the single CImg::draw_point() procedure,
       depending on the vectors size in \p points.
       \par Example:
       \code
       CImg<unsigned char> img(100,100,1,3,0);
       const unsigned char color[] = { 255,128,64 };
       CImgList<int> points;
       points.insert(CImg<int>::vector(0,0)).
             .insert(CImg<int>::vector(70,10)).
             .insert(CImg<int>::vector(80,60)).
             .insert(CImg<int>::vector(10,90));
       img.draw_point(points,color);
       \endcode
    **/
    template<typename t, typename tc>
    CImg<T>& draw_point(const CImgList<t>& points,
                        const tc *const color, const float opacity=1) {
      unsigned int H = ~0U; cimglist_for(points,p) H = cimg::min(H,(unsigned int)(points[p].size()));
      return _draw_point(points,points.size,H,color,opacity);
    }

    //! Draw a cloud of colored points.
    template<typename t, typename tc>
    CImg<T>& draw_point(const CImgList<t>& points,
                        const CImg<tc>& color, const float opacity=1) {
      return draw_point(points,color.data,opacity);
    }

    //! Draw a cloud of colored points.
    /**
       \note
       - Similar to the previous function, where the N vertex coordinates are stored as a Nx2 or Nx3 image
       (sequence of vectors aligned along the x-axis).
    **/
    template<typename t, typename tc>
    CImg<T>& draw_point(const CImg<t>& points,
                        const tc *const color, const float opacity=1) {
      return _draw_point(points,points.width,points.height,color,opacity);
    }

    //! Draw a cloud of colored points.
    template<typename t, typename tc>
    CImg<T>& draw_point(const CImg<t>& points,
                        const CImg<tc>& color, const float opacity=1) {
      return draw_point(points,color.data,opacity);
    }

    //! Draw a 2D colored line.
    /**
       \param x0 X-coordinate of the starting line point.
       \param y0 Y-coordinate of the starting line point.
       \param x1 X-coordinate of the ending line point.
       \param y1 Y-coordinate of the ending line point.
       \param color Pointer to \c dimv() consecutive values of type \c T, defining the drawing color.
       \param opacity Drawing opacity (optional).
       \param pattern An integer whose bits describe the line pattern (optional).
       \param init_hatch Flag telling if a reinitialization of the hash state must be done (optional).
       \note
       - Clipping is supported.
       - Line routine uses Bresenham's algorithm.
       - Set \p init_hatch = false to draw consecutive hatched segments without breaking the line pattern.
       \par Example:
       \code
       CImg<unsigned char> img(100,100,1,3,0);
       const unsigned char color[] = { 255,128,64 };
        img.draw_line(40,40,80,70,color);
       \endcode
    **/
    template<typename tc>
    CImg<T>& draw_line(const int x0, const int y0,
                       const int x1, const int y1,
                       const tc *const color, const float opacity=1,
                       const unsigned int pattern=~0U, const bool init_hatch=true) {
      if (is_empty()) return *this;
      if (!color)
        throw CImgArgumentException("CImg<%s>::draw_line() : Specified color is (null)",
                                    pixel_type());
      static unsigned int hatch = ~0U - (~0U>>1);
      if (init_hatch) hatch = ~0U - (~0U>>1);
      const bool xdir = x0<x1, ydir = y0<y1;
      int
        nx0 = x0, nx1 = x1, ny0 = y0, ny1 = y1,
        &xleft = xdir?nx0:nx1, &yleft = xdir?ny0:ny1,
        &xright = xdir?nx1:nx0, &yright = xdir?ny1:ny0,
        &xup = ydir?nx0:nx1, &yup = ydir?ny0:ny1,
        &xdown = ydir?nx1:nx0, &ydown = ydir?ny1:ny0;
      if (xright<0 || xleft>=dimx()) return *this;
      if (xleft<0) { yleft-=xleft*(yright - yleft)/(xright - xleft); xleft = 0; }
      if (xright>=dimx()) { yright-=(xright - dimx())*(yright - yleft)/(xright - xleft); xright = dimx()-1; }
      if (ydown<0 || yup>=dimy()) return *this;
      if (yup<0) { xup-=yup*(xdown - xup)/(ydown - yup); yup = 0; }
      if (ydown>=dimy()) { xdown-=(ydown - dimy())*(xdown - xup)/(ydown - yup); ydown = dimy()-1; }
      T *ptrd0 = ptr(nx0,ny0);
      int dx = xright - xleft, dy = ydown - yup;
      const bool steep = dy>dx;
      if (steep) cimg::swap(nx0,ny0,nx1,ny1,dx,dy);
      const int
        offx = (nx0<nx1?1:-1)*(steep?width:1),
        offy = (ny0<ny1?1:-1)*(steep?1:width),
        wh = width*height;
      if (opacity>=1) {
        if (~pattern) for (int error = dx>>1, x = 0; x<=dx; ++x) {
          if (pattern&hatch) { T *ptrd = ptrd0; const tc* col = color; cimg_forV(*this,k) { *ptrd = (T)*(col++); ptrd+=wh; }}
          hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1);
          ptrd0+=offx;
          if ((error-=dy)<0) { ptrd0+=offy; error+=dx; }
        } else for (int error = dx>>1, x = 0; x<=dx; ++x) {
          T *ptrd = ptrd0; const tc* col = color; cimg_forV(*this,k) { *ptrd = (T)*(col++); ptrd+=wh; }
          ptrd0+=offx;
          if ((error-=dy)<0) { ptrd0+=offy; error+=dx; }
        }
      } else {
        const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
        if (~pattern) for (int error = dx>>1, x = 0; x<=dx; ++x) {
          if (pattern&hatch) {
            T *ptrd = ptrd0; const tc* col = color;
            cimg_forV(*this,k) { *ptrd = (T)(nopacity**(col++) + *ptrd*copacity); ptrd+=wh; }
          }
          hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1);
          ptrd0+=offx;
          if ((error-=dy)<0) { ptrd0+=offy; error+=dx; }
        } else for (int error = dx>>1, x = 0; x<=dx; ++x) {
          T *ptrd = ptrd0; const tc* col = color; cimg_forV(*this,k) { *ptrd = (T)(nopacity**(col++) + *ptrd*copacity); ptrd+=wh; }
          ptrd0+=offx;
          if ((error-=dy)<0) { ptrd0+=offy; error+=dx; }
        }
      }
      return *this;
    }

    //! Draw a 2D colored line.
    template<typename tc>
    CImg<T>& draw_line(const int x0, const int y0,
                       const int x1, const int y1,
                       const CImg<tc>& color, const float opacity=1,
                       const unsigned int pattern=~0U, const bool init_hatch=true) {
      return draw_line(x0,y0,x1,y1,color.data,opacity,pattern,init_hatch);
    }

    //! Draw a 2D colored line, with z-buffering.
    template<typename tc>
    CImg<T>& draw_line(float *const zbuffer,
                       const int x0, const int y0, const float z0,
                       const int x1, const int y1, const float z1,
                       const tc *const color, const float opacity=1,
                       const unsigned int pattern=~0U, const bool init_hatch=true) {
      if (!is_empty() && z0>0 && z1>0) {
        if (!color)
          throw CImgArgumentException("CImg<%s>::draw_line() : Specified color is (null).",
                                      pixel_type());
        static unsigned int hatch = ~0U - (~0U>>1);
        if (init_hatch) hatch = ~0U - (~0U>>1);
        const bool xdir = x0<x1, ydir = y0<y1;
        int
          nx0 = x0, nx1 = x1, ny0 = y0, ny1 = y1,
          &xleft = xdir?nx0:nx1, &yleft = xdir?ny0:ny1,
          &xright = xdir?nx1:nx0, &yright = xdir?ny1:ny0,
          &xup = ydir?nx0:nx1, &yup = ydir?ny0:ny1,
          &xdown = ydir?nx1:nx0, &ydown = ydir?ny1:ny0;
        float
          Z0 = 1/z0, Z1 = 1/z1, nz0 = Z0, nz1 = Z1, dz = Z1 - Z0,
          &zleft = xdir?nz0:nz1,
          &zright = xdir?nz1:nz0,
          &zup = ydir?nz0:nz1,
          &zdown = ydir?nz1:nz0;
        if (xright<0 || xleft>=dimx()) return *this;
        if (xleft<0) {
          const int D = xright - xleft;
          yleft-=xleft*(yright - yleft)/D;
          zleft-=xleft*(zright - zleft)/D;
          xleft = 0;
        }
        if (xright>=dimx()) {
          const int d = xright - dimx(), D = xright - xleft;
          yright-=d*(yright - yleft)/D;
          zright-=d*(zright - zleft)/D;
          xright = dimx()-1;
        }
        if (ydown<0 || yup>=dimy()) return *this;
        if (yup<0) {
          const int D = ydown - yup;
          xup-=yup*(xdown - xup)/D;
          zup-=yup*(zdown - zup)/D;
          yup = 0;
        }
        if (ydown>=dimy()) {
          const int d = ydown - dimy(), D = ydown - yup;
          xdown-=d*(xdown - xup)/D;
          zdown-=d*(zdown - zup)/D;
          ydown = dimy()-1;
        }
        T *ptrd0 = ptr(nx0,ny0);
        float *ptrz = zbuffer + nx0 + ny0*width;
        int dx = xright - xleft, dy = ydown - yup;
        const bool steep = dy>dx;
        if (steep) cimg::swap(nx0,ny0,nx1,ny1,dx,dy);
        const int
          offx = (nx0<nx1?1:-1)*(steep?width:1),
          offy = (ny0<ny1?1:-1)*(steep?1:width),
          wh = width*height,
          ndx = dx>0?dx:1;
        if (opacity>=1) {
          if (~pattern) for (int error = dx>>1, x = 0; x<=dx; ++x) {
            const float z = Z0 + x*dz/ndx;
            if (z>*ptrz && pattern&hatch) {
              *ptrz = z;
              T *ptrd = ptrd0; const tc *col = color;
              cimg_forV(*this,k) { *ptrd = (T)*(col++); ptrd+=wh; }
            }
            hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1);
            ptrd0+=offx; ptrz+=offx;
            if ((error-=dy)<0) { ptrd0+=offy; ptrz+=offy; error+=dx; }
          } else for (int error = dx>>1, x = 0; x<=dx; ++x) {
            const float z = Z0 + x*dz/ndx;
            if (z>*ptrz) {
              *ptrz = z;
              T *ptrd = ptrd0; const tc *col = color;
              cimg_forV(*this,k) { *ptrd = (T)*(col++); ptrd+=wh; }
            }
            ptrd0+=offx; ptrz+=offx;
            if ((error-=dy)<0) { ptrd0+=offy; ptrz+=offy; error+=dx; }
          }
        } else {
          const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
          if (~pattern) for (int error = dx>>1, x = 0; x<=dx; ++x) {
            const float z = Z0 + x*dz/ndx;
            if (z>*ptrz && pattern&hatch) {
              *ptrz = z;
              T *ptrd = ptrd0; const tc *col = color;
              cimg_forV(*this,k) { *ptrd = (T)(nopacity**(col++) + *ptrd*copacity); ptrd+=wh; }
            }
            hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1);
            ptrd0+=offx; ptrz+=offx;
            if ((error-=dy)<0) { ptrd0+=offy; ptrz+=offy; error+=dx; }
          } else for (int error = dx>>1, x = 0; x<=dx; ++x) {
            const float z = Z0 + x*dz/ndx;
            if (z>*ptrz) {
              *ptrz = z;
              T *ptrd = ptrd0; const tc *col = color;
              cimg_forV(*this,k) { *ptrd = (T)(nopacity**(col++) + *ptrd*copacity); ptrd+=wh; }
            }
            ptrd0+=offx; ptrz+=offx;
            if ((error-=dy)<0) { ptrd0+=offy; ptrz+=offy; error+=dx; }
          }
        }
      }
      return *this;
    }

    //! Draw a 2D colored line, with z-buffering.
    template<typename tc>
    CImg<T>& draw_line(float *const zbuffer,
                       const int x0, const int y0, const float z0,
                       const int x1, const int y1, const float z1,
                       const CImg<tc>& color, const float opacity=1,
                       const unsigned int pattern=~0U, const bool init_hatch=true) {
      return draw_line(zbuffer,x0,y0,z0,x1,y1,z1,color.data,opacity,pattern,init_hatch);
    }

    //! Draw a 3D colored line.
    template<typename tc>
    CImg<T>& draw_line(const int x0, const int y0, const int z0,
                       const int x1, const int y1, const int z1,
                       const tc *const color, const float opacity=1,
                       const unsigned int pattern=~0U, const bool init_hatch=true) {
      if (is_empty()) return *this;
      if (!color)
        throw CImgArgumentException("CImg<%s>::draw_line() : Specified color is (null)",
                                    pixel_type());
      static unsigned int hatch = ~0U - (~0U>>1);
      if (init_hatch) hatch = ~0U - (~0U>>1);
      int nx0 = x0, ny0 = y0, nz0 = z0, nx1 = x1, ny1 = y1, nz1 = z1;
      if (nx0>nx1) cimg::swap(nx0,nx1,ny0,ny1,nz0,nz1);
      if (nx1<0 || nx0>=dimx()) return *this;
      if (nx0<0) { const int D = 1 + nx1 - nx0; ny0-=nx0*(1 + ny1 - ny0)/D; nz0-=nx0*(1 + nz1 - nz0)/D; nx0 = 0; }
      if (nx1>=dimx()) { const int d = nx1-dimx(), D = 1 + nx1 - nx0; ny1+=d*(1 + ny0 - ny1)/D; nz1+=d*(1 + nz0 - nz1)/D; nx1 = dimx()-1; }
      if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,nz0,nz1);
      if (ny1<0 || ny0>=dimy()) return *this;
      if (ny0<0) { const int D = 1 + ny1 - ny0; nx0-=ny0*(1 + nx1 - nx0)/D; nz0-=ny0*(1 + nz1 - nz0)/D; ny0 = 0; }
      if (ny1>=dimy()) { const int d = ny1-dimy(), D = 1 + ny1 - ny0; nx1+=d*(1 + nx0 - nx1)/D; nz1+=d*(1 + nz0 - nz1)/D; ny1 = dimy()-1; }
      if (nz0>nz1) cimg::swap(nx0,nx1,ny0,ny1,nz0,nz1);
      if (nz1<0 || nz0>=dimz()) return *this;
      if (nz0<0) { const int D = 1 + nz1 - nz0; nx0-=nz0*(1 + nx1 - nx0)/D; ny0-=nz0*(1 + ny1 - ny0)/D; nz0 = 0; }
      if (nz1>=dimz()) { const int d = nz1-dimz(), D = 1 + nz1 - nz0; nx1+=d*(1 + nx0 - nx1)/D; ny1+=d*(1 + ny0 - ny1)/D; nz1 = dimz()-1; }
      const unsigned int dmax = cimg::max(cimg::abs(nx1 - nx0),cimg::abs(ny1 - ny0),nz1 - nz0), whz = width*height*depth;
      const float px = (nx1 - nx0)/(float)dmax, py = (ny1 - ny0)/(float)dmax, pz = (nz1 - nz0)/(float)dmax;
      float x = (float)nx0, y = (float)ny0, z = (float)nz0;
      if (opacity>=1) for (unsigned int t = 0; t<=dmax; ++t) {
        if (!(~pattern) || (~pattern && pattern&hatch)) {
          T* ptrd = ptr((unsigned int)x,(unsigned int)y,(unsigned int)z);
          const tc *col = color; cimg_forV(*this,k) { *ptrd = (T)*(col++); ptrd+=whz; }
        }
        x+=px; y+=py; z+=pz; if (pattern) { hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1); }
      } else {
        const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
        for (unsigned int t = 0; t<=dmax; ++t) {
          if (!(~pattern) || (~pattern && pattern&hatch)) {
            T* ptrd = ptr((unsigned int)x,(unsigned int)y,(unsigned int)z);
            const tc *col = color; cimg_forV(*this,k) { *ptrd = (T)(*(col++)*nopacity + *ptrd*copacity); ptrd+=whz; }
          }
          x+=px; y+=py; z+=pz; if (pattern) { hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1); }
        }
      }
      return *this;
    }

    //! Draw a 3D colored line.
    template<typename tc>
    CImg<T>& draw_line(const int x0, const int y0, const int z0,
                       const int x1, const int y1, const int z1,
                       const CImg<tc>& color, const float opacity=1,
                       const unsigned int pattern=~0U, const bool init_hatch=true) {
      return draw_line(x0,y0,z0,x1,y1,z1,color.data,opacity,pattern,init_hatch);
    }

    //! Draw a 2D textured line.
    /**
       \param x0 X-coordinate of the starting line point.
       \param y0 Y-coordinate of the starting line point.
       \param x1 X-coordinate of the ending line point.
       \param y1 Y-coordinate of the ending line point.
       \param texture Texture image defining the pixel colors.
       \param tx0 X-coordinate of the starting texture point.
       \param ty0 Y-coordinate of the starting texture point.
       \param tx1 X-coordinate of the ending texture point.
       \param ty1 Y-coordinate of the ending texture point.
       \param opacity Drawing opacity (optional).
       \param pattern An integer whose bits describe the line pattern (optional).
       \param init_hatch Flag telling if the hash variable must be reinitialized (optional).
       \note
       - Clipping is supported but not for texture coordinates.
       - Line routine uses the well known Bresenham's algorithm.
       \par Example:
       \code
       CImg<unsigned char> img(100,100,1,3,0), texture("texture256x256.ppm");
       const unsigned char color[] = { 255,128,64 };
       img.draw_line(40,40,80,70,texture,0,0,255,255);
       \endcode
    **/
    template<typename tc>
    CImg<T>& draw_line(const int x0, const int y0,
                       const int x1, const int y1,
                       const CImg<tc>& texture,
                       const int tx0, const int ty0,
                       const int tx1, const int ty1,
                       const float opacity=1,
                       const unsigned int pattern=~0U, const bool init_hatch=true) {
      if (is_empty()) return *this;
      if (!texture || texture.dim<dim)
        throw CImgArgumentException("CImg<%s>::draw_line() : Specified texture (%u,%u,%u,%u,%p) is not a valid argument.",
                                    pixel_type(),texture.width,texture.height,texture.depth,texture.dim,texture.data);
      if (is_overlapped(texture)) return draw_line(x0,y0,x1,y1,+texture,tx0,ty0,tx1,ty1,opacity,pattern,init_hatch);
      static unsigned int hatch = ~0U - (~0U>>1);
      if (init_hatch) hatch = ~0U - (~0U>>1);
      const bool xdir = x0<x1, ydir = y0<y1;
      int
        dtx = tx1-tx0, dty = ty1-ty0,
        nx0 = x0, nx1 = x1, ny0 = y0, ny1 = y1,
        tnx0 = tx0, tnx1 = tx1, tny0 = ty0, tny1 = ty1,
        &xleft = xdir?nx0:nx1, &yleft = xdir?ny0:ny1, &xright = xdir?nx1:nx0, &yright = xdir?ny1:ny0,
        &txleft = xdir?tnx0:tnx1, &tyleft = xdir?tny0:tny1, &txright = xdir?tnx1:tnx0, &tyright = xdir?tny1:tny0,
        &xup = ydir?nx0:nx1, &yup = ydir?ny0:ny1, &xdown = ydir?nx1:nx0, &ydown = ydir?ny1:ny0,
        &txup = ydir?tnx0:tnx1, &tyup = ydir?tny0:tny1, &txdown = ydir?tnx1:tnx0, &tydown = ydir?tny1:tny0;
      if (xright<0 || xleft>=dimx()) return *this;
      if (xleft<0) {
        const int D = xright - xleft;
        yleft-=xleft*(yright - yleft)/D;
        txleft-=xleft*(txright - txleft)/D;
        tyleft-=xleft*(tyright - tyleft)/D;
        xleft = 0;
      }
      if (xright>=dimx()) {
        const int d = xright - dimx(), D = xright - xleft;
        yright-=d*(yright - yleft)/D;
        txright-=d*(txright - txleft)/D;
        tyright-=d*(tyright - tyleft)/D;
        xright = dimx()-1;
      }
      if (ydown<0 || yup>=dimy()) return *this;
      if (yup<0) {
        const int D = ydown - yup;
        xup-=yup*(xdown - xup)/D;
        txup-=yup*(txdown - txup)/D;
        tyup-=yup*(tydown - tyup)/D;
        yup = 0;
      }
      if (ydown>=dimy()) {
        const int d = ydown - dimy(), D = ydown - yup;
        xdown-=d*(xdown - xup)/D;
        txdown-=d*(txdown - txup)/D;
        tydown-=d*(tydown - tyup)/D;
        ydown = dimy()-1;
      }
      T *ptrd0 = ptr(nx0,ny0);
      int dx = xright - xleft, dy = ydown - yup;
      const bool steep = dy>dx;
      if (steep) cimg::swap(nx0,ny0,nx1,ny1,dx,dy);
      const int
        offx = (nx0<nx1?1:-1)*(steep?width:1),
        offy = (ny0<ny1?1:-1)*(steep?1:width),
        wh = width*height,
        ndx = dx>0?dx:1;
      if (opacity>=1) {
        if (~pattern) for (int error = dx>>1, x = 0; x<=dx; ++x) {
          if (pattern&hatch) {
            T *ptrd = ptrd0;
            const int tx = tx0 + x*dtx/ndx, ty = ty0 + x*dty/ndx;
            cimg_forV(*this,k) { *ptrd = (T)texture(tx,ty,0,k); ptrd+=wh; }
          }
          hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1);
          ptrd0+=offx;
          if ((error-=dy)<0) { ptrd0+=offy; error+=dx; }
        } else for (int error = dx>>1, x = 0; x<=dx; ++x) {
          T *ptrd = ptrd0;
          const int tx = tx0 + x*dtx/ndx, ty = ty0 + x*dty/ndx;
          cimg_forV(*this,k) { *ptrd = (T)texture(tx,ty,0,k); ptrd+=wh; }
          ptrd0+=offx;
          if ((error-=dy)<0) { ptrd0+=offy; error+=dx; }
        }
      } else {
        const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
        if (~pattern) for (int error = dx>>1, x = 0; x<=dx; ++x) {
          T *ptrd = ptrd0;
          if (pattern&hatch) {
            const int tx = tx0 + x*dtx/ndx, ty = ty0 + x*dty/ndx;
            cimg_forV(*this,k) { *ptrd = (T)(nopacity*texture(tx,ty,0,k) + *ptrd*copacity); ptrd+=wh; }
          }
          hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1);
          ptrd0+=offx;
          if ((error-=dy)<0) { ptrd0+=offy; error+=dx; }
        } else for (int error = dx>>1, x = 0; x<=dx; ++x) {
          T *ptrd = ptrd0;
          const int tx = tx0 + x*dtx/ndx, ty = ty0 + x*dty/ndx;
          cimg_forV(*this,k) { *ptrd = (T)(nopacity*texture(tx,ty,0,k) + *ptrd*copacity); ptrd+=wh; }
          ptrd0+=offx;
          if ((error-=dy)<0) { ptrd0+=offy; error+=dx; }
        }
      }
      return *this;
    }

    //! Draw a 2D textured line, with perspective correction.
    template<typename tc>
    CImg<T>& draw_line(const int x0, const int y0, const float z0,
                       const int x1, const int y1, const float z1,
                       const CImg<tc>& texture,
                       const int tx0, const int ty0,
                       const int tx1, const int ty1,
                       const float opacity=1,
                       const unsigned int pattern=~0U, const bool init_hatch=true) {
      if (is_empty() && z0<=0 && z1<=0) return *this;
      if (!texture || texture.dim<dim)
        throw CImgArgumentException("CImg<%s>::draw_line() : Specified texture (%u,%u,%u,%u,%p) is not a valid argument.",
                                    pixel_type(),texture.width,texture.height,texture.depth,texture.dim,texture.data);
      if (is_overlapped(texture)) return draw_line(x0,y0,z0,x1,y1,z1,+texture,tx0,ty0,tx1,ty1,opacity,pattern,init_hatch);
      static unsigned int hatch = ~0U - (~0U>>1);
      if (init_hatch) hatch = ~0U - (~0U>>1);
      const bool xdir = x0<x1, ydir = y0<y1;
      int
        nx0 = x0, nx1 = x1, ny0 = y0, ny1 = y1,
        &xleft = xdir?nx0:nx1, &yleft = xdir?ny0:ny1,
        &xright = xdir?nx1:nx0, &yright = xdir?ny1:ny0,
        &xup = ydir?nx0:nx1, &yup = ydir?ny0:ny1,
        &xdown = ydir?nx1:nx0, &ydown = ydir?ny1:ny0;
      float
        Tx0 = tx0/z0, Tx1 = tx1/z1,
        Ty0 = ty0/z0, Ty1 = ty1/z1,
        Z0 = 1/z0, Z1 = 1/z1,
        dz = Z1 - Z0, dtx = Tx1 - Tx0, dty = Ty1 - Ty0,
        tnx0 = Tx0, tnx1 = Tx1, tny0 = Ty0, tny1 = Ty1, nz0 = Z0, nz1 = Z1,
        &zleft = xdir?nz0:nz1, &txleft = xdir?tnx0:tnx1, &tyleft = xdir?tny0:tny1,
        &zright = xdir?nz1:nz0, &txright = xdir?tnx1:tnx0, &tyright = xdir?tny1:tny0,
        &zup = ydir?nz0:nz1, &txup = ydir?tnx0:tnx1, &tyup = ydir?tny0:tny1,
        &zdown = ydir?nz1:nz0, &txdown = ydir?tnx1:tnx0, &tydown = ydir?tny1:tny0;
      if (xright<0 || xleft>=dimx()) return *this;
      if (xleft<0) {
        const int D = xright - xleft;
        yleft-=xleft*(yright - yleft)/D;
        zleft-=xleft*(zright - zleft)/D;
        txleft-=xleft*(txright - txleft)/D;
        tyleft-=xleft*(tyright - tyleft)/D;
        xleft = 0;
      }
      if (xright>=dimx()) {
        const int d = xright - dimx(), D = xright - xleft;
        yright-=d*(yright - yleft)/D;
        zright-=d*(zright - zleft)/D;
        txright-=d*(txright - txleft)/D;
        tyright-=d*(tyright - tyleft)/D;
        xright = dimx()-1;
      }
      if (ydown<0 || yup>=dimy()) return *this;
      if (yup<0) {
        const int D = ydown - yup;
        xup-=yup*(xdown - xup)/D;
        zup-=yup*(zdown - zup)/D;
        txup-=yup*(txdown - txup)/D;
        tyup-=yup*(tydown - tyup)/D;
        yup = 0;
      }
      if (ydown>=dimy()) {
        const int d = ydown - dimy(), D = ydown - yup;
        xdown-=d*(xdown - xup)/D;
        zdown-=d*(zdown - zup)/D;
        txdown-=d*(txdown - txup)/D;
        tydown-=d*(tydown - tyup)/D;
        ydown = dimy()-1;
      }
      T *ptrd0 = ptr(nx0,ny0);
      int dx = xright - xleft, dy = ydown - yup;
      const bool steep = dy>dx;
      if (steep) cimg::swap(nx0,ny0,nx1,ny1,dx,dy);
      const int
        offx = (nx0<nx1?1:-1)*(steep?width:1),
        offy = (ny0<ny1?1:-1)*(steep?1:width),
        wh = width*height,
        ndx = dx>0?dx:1;
      if (opacity>=1) {
        if (~pattern) for (int error = dx>>1, x = 0; x<=dx; ++x) {
          if (pattern&hatch) {
            const float z = Z0 + x*dz/ndx, tx = Tx0 + x*dtx/ndx, ty = Ty0 + x*dty/ndx;
            T *ptrd = ptrd0; cimg_forV(*this,k) { *ptrd = (T)texture((int)(tx/z),(int)(ty/z),0,k); ptrd+=wh; }
          }
          hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1);
          ptrd0+=offx;
          if ((error-=dy)<0) { ptrd0+=offy; error+=dx; }
        } else for (int error = dx>>1, x = 0; x<=dx; ++x) {
          const float z = Z0 + x*dz/ndx, tx = Tx0 + x*dtx/ndx, ty = Ty0 + x*dty/ndx;
          T *ptrd = ptrd0; cimg_forV(*this,k) { *ptrd = (T)texture((int)(tx/z),(int)(ty/z),0,k); ptrd+=wh; }
          ptrd0+=offx;
          if ((error-=dy)<0) { ptrd0+=offy; error+=dx; }
        }
      } else {
        const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
        if (~pattern) for (int error = dx>>1, x = 0; x<=dx; ++x) {
          if (pattern&hatch) {
            const float z = Z0 + x*dz/ndx, tx = Tx0 + x*dtx/ndx, ty = Ty0 + x*dty/ndx;
            T *ptrd = ptrd0; cimg_forV(*this,k) { *ptrd = (T)(nopacity*texture((int)(tx/z),(int)(ty/z),0,k) + *ptrd*copacity); ptrd+=wh; }
          }
          hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1);
          ptrd0+=offx;
          if ((error-=dy)<0) { ptrd0+=offy; error+=dx; }
        } else for (int error = dx>>1, x = 0; x<=dx; ++x) {
          const float z = Z0 + x*dz/ndx, tx = Tx0 + x*dtx/ndx, ty = Ty0 + x*dty/ndx;
          T *ptrd = ptrd0;
          cimg_forV(*this,k) { *ptrd = (T)(nopacity*texture((int)(tx/z),(int)(ty/z),0,k) + *ptrd*copacity); ptrd+=wh; }
          ptrd0+=offx;
          if ((error-=dy)<0) { ptrd0+=offy; error+=dx; }
        }
      }
      return *this;
    }

    //! Draw a 2D textured line, with z-buffering and perspective correction.
    template<typename tc>
    CImg<T>& draw_line(float *const zbuffer,
                       const int x0, const int y0, const float z0,
                       const int x1, const int y1, const float z1,
                       const CImg<tc>& texture,
                       const int tx0, const int ty0,
                       const int tx1, const int ty1,
                       const float opacity=1,
                       const unsigned int pattern=~0U, const bool init_hatch=true) {
      if (!is_empty() && z0>0 && z1>0) {
        if (!texture || texture.dim<dim)
          throw CImgArgumentException("CImg<%s>::draw_line() : Specified texture (%u,%u,%u,%u,%p) is not a valid argument.",
                                      pixel_type(),texture.width,texture.height,texture.depth,texture.dim,texture.data);
        if (is_overlapped(texture)) return draw_line(zbuffer,x0,y0,z0,x1,y1,z1,+texture,tx0,ty0,tx1,ty1,opacity,pattern,init_hatch);
        static unsigned int hatch = ~0U - (~0U>>1);
        if (init_hatch) hatch = ~0U - (~0U>>1);
        const bool xdir = x0<x1, ydir = y0<y1;
        int
          nx0 = x0, nx1 = x1, ny0 = y0, ny1 = y1,
          &xleft = xdir?nx0:nx1, &yleft = xdir?ny0:ny1,
          &xright = xdir?nx1:nx0, &yright = xdir?ny1:ny0,
          &xup = ydir?nx0:nx1, &yup = ydir?ny0:ny1,
          &xdown = ydir?nx1:nx0, &ydown = ydir?ny1:ny0;
        float
          Tx0 = tx0/z0, Tx1 = tx1/z1,
          Ty0 = ty0/z0, Ty1 = ty1/z1,
          Z0 = 1/z0, Z1 = 1/z1,
          dz = Z1 - Z0, dtx = Tx1 - Tx0, dty = Ty1 - Ty0,
          tnx0 = Tx0, tnx1 = Tx1, tny0 = Ty0, tny1 = Ty1, nz0 = Z0, nz1 = Z1,
          &zleft = xdir?nz0:nz1, &txleft = xdir?tnx0:tnx1, &tyleft = xdir?tny0:tny1,
          &zright = xdir?nz1:nz0, &txright = xdir?tnx1:tnx0, &tyright = xdir?tny1:tny0,
          &zup = ydir?nz0:nz1, &txup = ydir?tnx0:tnx1, &tyup = ydir?tny0:tny1,
          &zdown = ydir?nz1:nz0, &txdown = ydir?tnx1:tnx0, &tydown = ydir?tny1:tny0;
        if (xright<0 || xleft>=dimx()) return *this;
        if (xleft<0) {
          const int D = xright - xleft;
          yleft-=xleft*(yright - yleft)/D;
          zleft-=xleft*(zright - zleft)/D;
          txleft-=xleft*(txright - txleft)/D;
          tyleft-=xleft*(tyright - tyleft)/D;
          xleft = 0;
        }
        if (xright>=dimx()) {
          const int d = xright - dimx(), D = xright - xleft;
          yright-=d*(yright - yleft)/D;
          zright-=d*(zright - zleft)/D;
          txright-=d*(txright - txleft)/D;
          tyright-=d*(tyright - tyleft)/D;
          xright = dimx()-1;
        }
        if (ydown<0 || yup>=dimy()) return *this;
        if (yup<0) {
          const int D = ydown - yup;
          xup-=yup*(xdown - xup)/D;
          zup-=yup*(zdown - zup)/D;
          txup-=yup*(txdown - txup)/D;
          tyup-=yup*(tydown - tyup)/D;
          yup = 0;
        }
        if (ydown>=dimy()) {
          const int d = ydown - dimy(), D = ydown - yup;
          xdown-=d*(xdown - xup)/D;
          zdown-=d*(zdown - zup)/D;
          txdown-=d*(txdown - txup)/D;
          tydown-=d*(tydown - tyup)/D;
          ydown = dimy()-1;
        }
        T *ptrd0 = ptr(nx0,ny0);
        float *ptrz = zbuffer + nx0 + ny0*width;
        int dx = xright - xleft, dy = ydown - yup;
        const bool steep = dy>dx;
        if (steep) cimg::swap(nx0,ny0,nx1,ny1,dx,dy);
        const int
          offx = (nx0<nx1?1:-1)*(steep?width:1),
          offy = (ny0<ny1?1:-1)*(steep?1:width),
          wh = width*height,
          ndx = dx>0?dx:1;
        if (opacity>=1) {
          if (~pattern) for (int error = dx>>1, x = 0; x<=dx; ++x) {
            if (pattern&hatch) {
              const float z = Z0 + x*dz/ndx;
              if (z>*ptrz) {
                *ptrz = z;
                const float tx = Tx0 + x*dtx/ndx, ty = Ty0 + x*dty/ndx;
                T *ptrd = ptrd0; cimg_forV(*this,k) { *ptrd = (T)texture((int)(tx/z),(int)(ty/z),0,k); ptrd+=wh; }
              }
            }
            hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1);
            ptrd0+=offx; ptrz+=offx;
            if ((error-=dy)<0) { ptrd0+=offy; ptrz+=offy; error+=dx; }
          } else for (int error = dx>>1, x = 0; x<=dx; ++x) {
            const float z = Z0 + x*dz/ndx;
            if (z>*ptrz) {
              *ptrz = z;
              const float tx = Tx0 + x*dtx/ndx, ty = Ty0 + x*dty/ndx;
              T *ptrd = ptrd0; cimg_forV(*this,k) { *ptrd = (T)texture((int)(tx/z),(int)(ty/z),0,k); ptrd+=wh; }
            }
            ptrd0+=offx; ptrz+=offx;
            if ((error-=dy)<0) { ptrd0+=offy; ptrz+=offy; error+=dx; }
          }
        } else {
          const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
          if (~pattern) for (int error = dx>>1, x = 0; x<=dx; ++x) {
            if (pattern&hatch) {
              const float z = Z0 + x*dz/ndx;
              if (z>*ptrz) {
                *ptrz = z;
                const float tx = Tx0 + x*dtx/ndx, ty = Ty0 + x*dty/ndx;
                T *ptrd = ptrd0; cimg_forV(*this,k) { *ptrd = (T)(nopacity*texture((int)(tx/z),(int)(ty/z),0,k) + *ptrd*copacity); ptrd+=wh; }
              }
            }
            hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1);
            ptrd0+=offx; ptrz+=offx;
            if ((error-=dy)<0) { ptrd0+=offy; ptrz+=offy; error+=dx; }
          } else for (int error = dx>>1, x = 0; x<=dx; ++x) {
            const float z = Z0 + x*dz/ndx;
            if (z>*ptrz) {
              *ptrz = z;
              const float tx = Tx0 + x*dtx/ndx, ty = Ty0 + x*dty/ndx;
              T *ptrd = ptrd0; cimg_forV(*this,k) { *ptrd = (T)(nopacity*texture((int)(tx/z),(int)(ty/z),0,k) + *ptrd*copacity); ptrd+=wh; }
            }
            ptrd0+=offx; ptrz+=offx;
            if ((error-=dy)<0) { ptrd0+=offy; ptrz+=offx; error+=dx; }
          }
        }
      }
      return *this;
    }

    // Inner routine for drawing set of consecutive lines with generic type for coordinates.
    template<typename t, typename tc>
    CImg<T>& _draw_line(const t& points, const unsigned int W, const unsigned int H,
                        const tc *const color, const float opacity,
                        const unsigned int pattern, const bool init_hatch) {
      if (is_empty() || !points || W<2) return *this;
      bool ninit_hatch = init_hatch;
      switch (H) {
      case 0 : case 1 :
        throw CImgArgumentException("CImg<%s>::draw_line() : Given list of points is not valid.",
                                    pixel_type());
      case 2 : {
        const int x0 = (int)points(0,0), y0 = (int)points(0,1);
        int ox = x0, oy = y0;
        for (unsigned int i = 1; i<W; ++i) {
          const int x = (int)points(i,0), y = (int)points(i,1);
          draw_line(ox,oy,x,y,color,opacity,pattern,ninit_hatch);
          ninit_hatch = false;
          ox = x; oy = y;
        }
      } break;
      default : {
        const int x0 = (int)points(0,0), y0 = (int)points(0,1), z0 = (int)points(0,2);
        int ox = x0, oy = y0, oz = z0;
        for (unsigned int i = 1; i<W; ++i) {
          const int x = (int)points(i,0), y = (int)points(i,1), z = (int)points(i,2);
          draw_line(ox,oy,oz,x,y,z,color,opacity,pattern,ninit_hatch);
          ninit_hatch = false;
          ox = x; oy = y; oz = z;
        }
      }
      }
      return *this;
    }

    //! Draw a set of consecutive colored lines in the instance image.
    /**
       \param points Coordinates of vertices, stored as a list of vectors.
       \param color Pointer to \c dimv() consecutive values of type \c T, defining the drawing color.
       \param opacity Drawing opacity (optional).
       \param pattern An integer whose bits describe the line pattern (optional).
       \param init_hatch If set to true, init hatch motif.
       \note
       - This function uses several call to the single CImg::draw_line() procedure,
       depending on the vectors size in \p points.
       \par Example:
       \code
       CImg<unsigned char> img(100,100,1,3,0);
       const unsigned char color[] = { 255,128,64 };
       CImgList<int> points;
       points.insert(CImg<int>::vector(0,0)).
             .insert(CImg<int>::vector(70,10)).
             .insert(CImg<int>::vector(80,60)).
             .insert(CImg<int>::vector(10,90));
       img.draw_line(points,color);
       \endcode
    **/
    template<typename t, typename tc>
    CImg<T>& draw_line(const CImgList<t>& points,
                       const tc *const color, const float opacity=1,
                       const unsigned int pattern=~0U, const bool init_hatch=true) {
      unsigned int H = ~0U; cimglist_for(points,p) H = cimg::min(H,(unsigned int)(points[p].size()));
      return _draw_line(points,points.size,H,color,opacity,pattern,init_hatch);
    }

    //! Draw a set of consecutive colored lines in the instance image.
    template<typename t, typename tc>
    CImg<T>& draw_line(const CImgList<t>& points,
                       const CImg<tc>& color, const float opacity=1,
                       const unsigned int pattern=~0U, const bool init_hatch=true) {
      return draw_line(points,color.data,opacity,pattern,init_hatch);
    }

    //! Draw a set of consecutive colored lines in the instance image.
    /**
       \note
       - Similar to the previous function, where the N vertex coordinates are stored as a Nx2 or Nx3 image
       (sequence of vectors aligned along the x-axis).
    **/
    template<typename t, typename tc>
    CImg<T>& draw_line(const CImg<t>& points,
                       const tc *const color, const float opacity=1,
                       const unsigned int pattern=~0U, const bool init_hatch=true) {
      return _draw_line(points,points.width,points.height,color,opacity,pattern,init_hatch);
    }

    //! Draw a set of consecutive colored lines in the instance image.
    template<typename t, typename tc>
    CImg<T>& draw_line(const CImg<t>& points,
                       const CImg<tc>& color, const float opacity=1,
                       const unsigned int pattern=~0U, const bool init_hatch=true) {
      return draw_line(points,color.data,opacity,pattern,init_hatch);
    }

    // Inner routine for a drawing filled polygon with generic type for coordinates.
    template<typename t, typename tc>
    CImg<T>& _draw_polygon(const t& points, const unsigned int N,
                           const tc *const color, const float opacity) {
      if (is_empty() || !points || N<3) return *this;
      if (!color)
        throw CImgArgumentException("CImg<%s>::draw_polygon() : Specified color is (null).",
                                    pixel_type());
      _draw_scanline(color,opacity);
      int xmin = (int)(~0U>>1), xmax = 0, ymin = (int)(~0U>>1), ymax = 0;
      { for (unsigned int p = 0; p<N; ++p) {
        const int x = (int)points(p,0), y = (int)points(p,1);
        if (x<xmin) xmin = x;
        if (x>xmax) xmax = x;
        if (y<ymin) ymin = y;
        if (y>ymax) ymax = y;
      }}
      if (xmax<0 || xmin>=dimx() || ymax<0 || ymin>=dimy()) return *this;
      const unsigned int
        nymin = ymin<0?0:(unsigned int)ymin,
        nymax = ymax>=dimy()?height-1:(unsigned int)ymax,
        dy = 1 + nymax - nymin;
      CImg<intT> X(1+2*N,dy,1,1,0), tmp;
      int cx = (int)points(0,0), cy = (int)points(0,1);
      for (unsigned int cp = 0, p = 0; p<N; ++p) {
        const unsigned int np = (p!=N-1)?p+1:0, ap = (np!=N-1)?np+1:0;
        const int
          nx = (int)points(np,0), ny = (int)points(np,1), ay = (int)points(ap,1),
          y0 = cy - nymin, y1 = ny - nymin;
        if (y0!=y1) {
          const int countermin = ((ny<ay && cy<ny) || (ny>ay && cy>ny))?1:0;
          for (int x = cx, y = y0, _sx = 1, _sy = 1,
                 _dx = nx>cx?nx-cx:((_sx=-1),cx-nx),
                 _dy = y1>y0?y1-y0:((_sy=-1),y0-y1),
                 _counter = ((_dx-=_dy?_dy*(_dx/_dy):0),_dy),
                 _err = _dx>>1,
                 _rx = _dy?(nx-cx)/_dy:0;
               _counter>=countermin;
               --_counter, y+=_sy, x+=_rx + ((_err-=_dx)<0?_err+=_dy,_sx:0))
            if (y>=0 && y<(int)dy) X(++X(0,y),y) = x;
          cp = np; cx = nx; cy = ny;
        } else {
          const int pp = (cp?cp-1:N-1), py = (int)points(pp,1);
          if ((cy>py && ay>cy) || (cy<py && ay<cy)) X(++X(0,y0),y0) = nx;
          if (cy!=ay) { cp = np; cx = nx; cy = ny; }
        }
      }
      for (int y = 0; y<(int)dy; ++y) {
        tmp.assign(X.ptr(1,y),X(0,y),1,1,1,true).sort();
        for (int i = 1; i<=X(0,y); ) {
          const int xb = X(i++,y), xe = X(i++,y);
          _draw_scanline(xb,xe,nymin+y,color,opacity);
        }
      }
      return *this;
    }

    //! Draw a filled polygon in the instance image.
    template<typename t, typename tc>
    CImg<T>& draw_polygon(const CImgList<t>& points,
                          const tc *const color, const float opacity=1) {
      if (!points.is_sameY(2))
        throw CImgArgumentException("CImg<%s>::draw_polygon() : Given list of points is not valid.",
                                    pixel_type());
      return _draw_polygon(points,points.size,color,opacity);
    }

    //! Draw a filled polygon in the instance image.
    template<typename t, typename tc>
    CImg<T>& draw_polygon(const CImgList<t>& points,
                          const CImg<tc>& color, const float opacity=1) {
      return draw_polygon(points,color.data,opacity);
    }

    //! Draw a filled polygon in the instance image.
    template<typename t, typename tc>
    CImg<T>& draw_polygon(const CImg<t>& points,
                          const tc *const color, const float opacity=1) {
      if (points.height<2)
        throw CImgArgumentException("CImg<%s>::draw_polygon() : Given list of points is not valid.",
                                    pixel_type());
      return _draw_polygon(points,points.width,color,opacity);
    }

    //! Draw a filled polygon in the instance image.
    template<typename t, typename tc>
    CImg<T>& draw_polygon(const CImg<t>& points,
                          const CImg<tc>& color, const float opacity=1) {
      return draw_polygon(points,color.data,opacity);
    }

    // Inner routine for drawing an outlined polygon with generic point coordinates.
    template<typename t, typename tc>
    CImg<T>& _draw_polygon(const t& points, const unsigned int W, const unsigned int H,
                           const tc *const color, const float opacity,
                           const unsigned int pattern) {
      if (is_empty() || !points || W<3) return *this;
      bool ninit_hatch = true;
      switch (H) {
      case 0 : case 1 :
        throw CImgArgumentException("CImg<%s>::draw_polygon() : Given list of points is not valid.",
                                    pixel_type());
      case 2 : {
        const int x0 = (int)points(0,0), y0 = (int)points(0,1);
        int ox = x0, oy = y0;
        for (unsigned int i = 1; i<W; ++i) {
          const int x = (int)points(i,0), y = (int)points(i,1);
          draw_line(ox,oy,x,y,color,opacity,pattern,ninit_hatch);
          ninit_hatch = false;
          ox = x; oy = y;
        }
        draw_line(ox,oy,x0,y0,color,opacity,pattern,false);
      } break;
      default : {
        const int x0 = (int)points(0,0), y0 = (int)points(0,1), z0 = (int)points(0,2);
        int ox = x0, oy = y0, oz = z0;
        for (unsigned int i = 1; i<W; ++i) {
          const int x = (int)points(i,0), y = (int)points(i,1), z = (int)points(i,2);
          draw_line(ox,oy,oz,x,y,z,color,opacity,pattern,ninit_hatch);
          ninit_hatch = false;
          ox = x; oy = y; oz = z;
        }
        draw_line(ox,oy,oz,x0,y0,z0,color,opacity,pattern,false);
      }
      }
      return *this;
    }

    //! Draw a polygon outline.
    template<typename t, typename tc>
    CImg<T>& draw_polygon(const CImgList<t>& points,
                          const tc *const color, const float opacity,
                          const unsigned int pattern) {
      unsigned int H = ~0U; cimglist_for(points,p) H = cimg::min(H,(unsigned int)(points[p].size()));
      return _draw_polygon(points,points.size,H,color,opacity,pattern);
    }

    //! Draw a polygon outline.
    template<typename t, typename tc>
    CImg<T>& draw_polygon(const CImgList<t>& points,
                          const CImg<tc>& color, const float opacity,
                          const unsigned int pattern) {
      return draw_polygon(points,color.data,opacity,pattern);
    }

    //! Draw a polygon outline.
    template<typename t, typename tc>
    CImg<T>& draw_polygon(const CImg<t>& points,
                          const tc *const color, const float opacity,
                          const unsigned int pattern) {
      return _draw_polygon(points,points.width,points.height,color,opacity,pattern);
    }

    //! Draw a polygon outline.
    template<typename t, typename tc>
    CImg<T>& draw_polygon(const CImg<t>& points,
                          const CImg<tc>& color, const float opacity,
                          const unsigned int pattern) {
      return draw_polygon(points,color.data,opacity,pattern);
    }

    //! Draw a cubic spline curve in the instance image.
    /**
       \param x0 X-coordinate of the starting curve point
       \param y0 Y-coordinate of the starting curve point
       \param u0 X-coordinate of the starting velocity
       \param v0 Y-coordinate of the starting velocity
       \param x1 X-coordinate of the ending curve point
       \param y1 Y-coordinate of the ending curve point
       \param u1 X-coordinate of the ending velocity
       \param v1 Y-coordinate of the ending velocity
       \param color Pointer to \c dimv() consecutive values of type \c T, defining the drawing color.
       \param precision Curve drawing precision (optional).
       \param opacity Drawing opacity (optional).
       \param pattern An integer whose bits describe the line pattern (optional).
       \param init_hatch If \c true, init hatch motif.
       \note
       - The curve is a 2D cubic Bezier spline, from the set of specified starting/ending points
       and corresponding velocity vectors.
       - The spline is drawn as a serie of connected segments. The \p precision parameter sets the
       average number of pixels in each drawn segment.
       - A cubic Bezier curve is sometimes defined by a set of 4 points { (\p x0,\p y0), (\p xa,\p ya), (\p xb,\p yb), (\p x1,\p y1) }
       where (\p x0,\p y0) is the starting point, (\p x1,\p y1) is the ending point and (\p xa,\p ya), (\p xb,\p yb) are two
       \e control points.
       The starting and ending velocities (\p u0,\p v0) and (\p u1,\p v1) can be deduced easily from the control points as
       \p u0 = (\p xa - \p x0), \p v0 = (\p ya - \p y0), \p u1 = (\p x1 - \p xb) and \p v1 = (\p y1 - \p yb).
       \par Example:
       \code
       CImg<unsigned char> img(100,100,1,3,0);
       const unsigned char color[] = { 255,255,255 };
       img.draw_spline(30,30,0,100,90,40,0,-100,color);
       \endcode
    **/
    template<typename tc>
    CImg<T>& draw_spline(const int x0, const int y0, const float u0, const float v0,
                         const int x1, const int y1, const float u1, const float v1,
                         const tc *const color, const float opacity=1,
                         const float precision=4, const unsigned int pattern=~0U,
                         const bool init_hatch=true) {
      if (is_empty()) return *this;
      if (!color)
        throw CImgArgumentException("CImg<%s>::draw_spline() : Specified color is (null)",
                                    pixel_type());
      bool ninit_hatch = init_hatch;
      const float
        dx = (float)(x1 - x0),
        dy = (float)(y1 - y0),
        dmax = cimg::max(cimg::abs(dx),cimg::abs(dy)),
        ax = -2*dx + u0 + u1,
        bx = 3*dx - 2*u0 - u1,
        ay = -2*dy + v0 + v1,
        by = 3*dy - 2*v0 - v1,
        xprecision = dmax>0?precision/dmax:1.0f,
        tmax = 1 + (dmax>0?xprecision:0.0f);
      int ox = x0, oy = y0;
      for (float t = 0; t<tmax; t+=xprecision) {
        const float
          t2 = t*t,
          t3 = t2*t;
        const int
          nx = (int)(ax*t3 + bx*t2 + u0*t + x0),
          ny = (int)(ay*t3 + by*t2 + v0*t + y0);
        draw_line(ox,oy,nx,ny,color,opacity,pattern,ninit_hatch);
        ninit_hatch = false;
        ox = nx; oy = ny;
      }
      return *this;
    }

    //! Draw a cubic spline curve in the instance image.
    template<typename tc>
    CImg<T>& draw_spline(const int x0, const int y0, const float u0, const float v0,
                         const int x1, const int y1, const float u1, const float v1,
                         const CImg<tc>& color, const float opacity=1,
                         const float precision=4, const unsigned int pattern=~0U,
                         const bool init_hatch=true) {
      return draw_spline(x0,y0,u0,v0,x1,y1,u1,v1,color.data,opacity,precision,pattern,init_hatch);
    }

    //! Draw a cubic spline curve in the instance image (for volumetric images).
    /**
       \note
       - Similar to CImg::draw_spline() for a 3D spline in a volumetric image.
    **/
    template<typename tc>
    CImg<T>& draw_spline(const int x0, const int y0, const int z0, const float u0, const float v0, const float w0,
                         const int x1, const int y1, const int z1, const float u1, const float v1, const float w1,
                         const tc *const color, const float opacity=1,
                         const float precision=4, const unsigned int pattern=~0U,
                         const bool init_hatch=true) {
      if (is_empty()) return *this;
      if (!color)
        throw CImgArgumentException("CImg<%s>::draw_spline() : Specified color is (null)",
                                    pixel_type());
      bool ninit_hatch = init_hatch;
      const float
        dx = (float)(x1 - x0),
        dy = (float)(y1 - y0),
        dz = (float)(z1 - z0),
        dmax = cimg::max(cimg::abs(dx),cimg::abs(dy),cimg::abs(dz)),
        ax = -2*dx + u0 + u1,
        bx = 3*dx - 2*u0 - u1,
        ay = -2*dy + v0 + v1,
        by = 3*dy - 2*v0 - v1,
        az = -2*dz + w0 + w1,
        bz = 3*dz - 2*w0 - w1,
        xprecision = dmax>0?precision/dmax:1.0f,
        tmax = 1 + (dmax>0?xprecision:0.0f);
      int ox = x0, oy = y0, oz = z0;
      for (float t = 0; t<tmax; t+=xprecision) {
        const float
          t2 = t*t,
          t3 = t2*t;
        const int
          nx = (int)(ax*t3 + bx*t2 + u0*t + x0),
          ny = (int)(ay*t3 + by*t2 + v0*t + y0),
          nz = (int)(az*t3 + bz*t2 + w0*t + z0);
        draw_line(ox,oy,oz,nx,ny,nz,color,opacity,pattern,ninit_hatch);
        ninit_hatch = false;
        ox = nx; oy = ny; oz = nz;
      }
      return *this;
    }

    //! Draw a cubic spline curve in the instance image (for volumetric images).
    template<typename tc>
    CImg<T>& draw_spline(const int x0, const int y0, const int z0, const float u0, const float v0, const float w0,
                         const int x1, const int y1, const int z1, const float u1, const float v1, const float w1,
                         const CImg<tc>& color, const float opacity=1,
                         const float precision=4, const unsigned int pattern=~0U,
                         const bool init_hatch=true) {
      return draw_spline(x0,y0,z0,u0,v0,w0,x1,y1,z1,u1,v1,w1,color.data,opacity,precision,pattern,init_hatch);
    }

    //! Draw a cubic spline curve in the instance image.
    /**
       \param x0 X-coordinate of the starting curve point
       \param y0 Y-coordinate of the starting curve point
       \param u0 X-coordinate of the starting velocity
       \param v0 Y-coordinate of the starting velocity
       \param x1 X-coordinate of the ending curve point
       \param y1 Y-coordinate of the ending curve point
       \param u1 X-coordinate of the ending velocity
       \param v1 Y-coordinate of the ending velocity
       \param texture Texture image defining line pixel colors.
       \param tx0 X-coordinate of the starting texture point.
       \param ty0 Y-coordinate of the starting texture point.
       \param tx1 X-coordinate of the ending texture point.
       \param ty1 Y-coordinate of the ending texture point.
       \param precision Curve drawing precision (optional).
       \param opacity Drawing opacity (optional).
       \param pattern An integer whose bits describe the line pattern (optional).
       \param init_hatch if \c true, reinit hatch motif.
    **/
    template<typename t>
    CImg<T>& draw_spline(const int x0, const int y0, const float u0, const float v0,
                         const int x1, const int y1, const float u1, const float v1,
                         const CImg<t>& texture,
                         const int tx0, const int ty0, const int tx1, const int ty1,
                         const float opacity=1,
                         const float precision=4, const unsigned int pattern=~0U,
                         const bool init_hatch=true) {
      if (is_empty()) return *this;
      if (!texture || texture.dim<dim)
        throw CImgArgumentException("CImg<%s>::draw_line() : Specified texture (%u,%u,%u,%u,%p) is not a valid argument.",
                                    pixel_type(),texture.width,texture.height,texture.depth,texture.dim,texture.data);
      if (is_overlapped(texture)) return draw_spline(x0,y0,u0,v0,x1,y1,u1,v1,+texture,tx0,ty0,tx1,ty1,precision,opacity,pattern,init_hatch);
      bool ninit_hatch = true;
      const float
        dx = (float)(x1 - x0),
        dy = (float)(y1 - y0),
        dmax = cimg::max(cimg::abs(dx),cimg::abs(dy)),
        ax = -2*dx + u0 + u1,
        bx = 3*dx - 2*u0 - u1,
        ay = -2*dy + v0 + v1,
        by = 3*dy - 2*v0 - v1,
        xprecision = dmax>0?precision/dmax:1.0f,
        tmax = 1 + (dmax>0?xprecision:0.0f);
      int ox = x0, oy = y0, otx = tx0, oty = ty0;
      for (float t1 = 0; t1<tmax; t1+=xprecision) {
        const float
          t2 = t1*t1,
          t3 = t2*t1;
        const int
          nx = (int)(ax*t3 + bx*t2 + u0*t1 + x0),
          ny = (int)(ay*t3 + by*t2 + v0*t1 + y0),
          ntx = tx0 + (int)((tx1-tx0)*t1/tmax),
          nty = ty0 + (int)((ty1-ty0)*t1/tmax);
        draw_line(ox,oy,nx,ny,texture,otx,oty,ntx,nty,opacity,pattern,ninit_hatch);
        ninit_hatch = false;
        ox = nx; oy = ny; otx = ntx; oty = nty;
      }
      return *this;
    }

    // Draw a set of connected spline curves in the instance image (internal).
    template<typename tp, typename tt, typename tc>
    CImg<T>& _draw_spline(const tp& points, const tt& tangents, const unsigned int W, const unsigned int H,
                          const tc *const color, const float opacity,
                          const bool close_set, const float precision,
                          const unsigned int pattern, const bool init_hatch) {
      if (is_empty() || !points || !tangents || W<2) return *this;
      bool ninit_hatch = init_hatch;
      switch (H) {
      case 0 : case 1 :
        throw CImgArgumentException("CImg<%s>::draw_spline() : Given list of points or tangents is not valid.",
                                    pixel_type());
      case 2 : {
        const int x0 = (int)points(0,0), y0 = (int)points(0,1);
        const float u0 = (float)tangents(0,0), v0 = (float)tangents(0,1);
        int ox = x0, oy = y0;
        float ou = u0, ov = v0;
        for (unsigned int i = 1; i<W; ++i) {
          const int x = (int)points(i,0), y = (int)points(i,1);
          const float u = (float)tangents(i,0), v = (float)tangents(i,1);
          draw_spline(ox,oy,ou,ov,x,y,u,v,color,precision,opacity,pattern,ninit_hatch);
          ninit_hatch = false;
          ox = x; oy = y; ou = u; ov = v;
        }
        if (close_set) draw_spline(ox,oy,ou,ov,x0,y0,u0,v0,color,precision,opacity,pattern,false);
      } break;
      default : {
        const int x0 = (int)points(0,0), y0 = (int)points(0,1), z0 = (int)points(0,2);
        const float u0 = (float)tangents(0,0), v0 = (float)tangents(0,1), w0 = (float)tangents(0,2);
        int ox = x0, oy = y0, oz = z0;
        float ou = u0, ov = v0, ow = w0;
        for (unsigned int i = 1; i<W; ++i) {
          const int x = (int)points(i,0), y = (int)points(i,1), z = (int)points(i,2);
          const float u = (float)tangents(i,0), v = (float)tangents(i,1), w = (float)tangents(i,2);
          draw_spline(ox,oy,oz,ou,ov,ow,x,y,z,u,v,w,color,opacity,pattern,ninit_hatch);
          ninit_hatch = false;
          ox = x; oy = y; oz = z; ou = u; ov = v; ow = w;
        }
        if (close_set) draw_spline(ox,oy,oz,ou,ov,ow,x0,y0,z0,u0,v0,w0,color,precision,opacity,pattern,false);
      }
      }
      return *this;
    }

    // Draw a set of connected spline curves in the instance image (internal).
    template<typename tp, typename tc>
    CImg<T>& _draw_spline(const tp& points, const unsigned int W, const unsigned int H,
                          const tc *const color, const float opacity,
                          const bool close_set, const float precision,
                          const unsigned int pattern, const bool init_hatch) {
      if (is_empty() || !points || W<2) return *this;
      CImg<Tfloat> tangents;
      switch (H) {
      case 0 : case 1 :
        throw CImgArgumentException("CImg<%s>::draw_spline() : Given list of points or tangents is not valid.",
                                    pixel_type());
      case 2 : {
        tangents.assign(W,H);
        for (unsigned int p = 0; p<W; ++p) {
          const unsigned int
            p0 = close_set?(p+W-1)%W:(p?p-1:0),
            p1 = close_set?(p+1)%W:(p+1<W?p+1:p);
          const float
            x = (float)points(p,0),
            y = (float)points(p,1),
            x0 = (float)points(p0,0),
            y0 = (float)points(p0,1),
            x1 = (float)points(p1,0),
            y1 = (float)points(p1,1),
            u0 = x - x0,
            v0 = y - y0,
            n0 = 1e-8f + (float)cimg_std::sqrt(u0*u0 + v0*v0),
            u1 = x1 - x,
            v1 = y1 - y,
            n1 = 1e-8f + (float)cimg_std::sqrt(u1*u1 + v1*v1),
            u = u0/n0 + u1/n1,
            v = v0/n0 + v1/n1,
            n = 1e-8f + (float)cimg_std::sqrt(u*u + v*v),
            fact = 0.5f*(n0 + n1);
          tangents(p,0) = (Tfloat)(fact*u/n);
          tangents(p,1) = (Tfloat)(fact*v/n);
        }
      } break;
      default : {
        tangents.assign(W,H);
        for (unsigned int p = 0; p<W; ++p) {
          const unsigned int
            p0 = close_set?(p+W-1)%W:(p?p-1:0),
            p1 = close_set?(p+1)%W:(p+1<W?p+1:p);
          const float
            x = (float)points(p,0),
            y = (float)points(p,1),
            z = (float)points(p,2),
            x0 = (float)points(p0,0),
            y0 = (float)points(p0,1),
            z0 = (float)points(p0,2),
            x1 = (float)points(p1,0),
            y1 = (float)points(p1,1),
            z1 = (float)points(p1,2),
            u0 = x - x0,
            v0 = y - y0,
            w0 = z - z0,
            n0 = 1e-8f + (float)cimg_std::sqrt(u0*u0 + v0*v0 + w0*w0),
            u1 = x1 - x,
            v1 = y1 - y,
            w1 = z1 - z,
            n1 = 1e-8f + (float)cimg_std::sqrt(u1*u1 + v1*v1 + w1*w1),
            u = u0/n0 + u1/n1,
            v = v0/n0 + v1/n1,
            w = w0/n0 + w1/n1,
            n = 1e-8f + (float)cimg_std::sqrt(u*u + v*v + w*w),
            fact = 0.5f*(n0 + n1);
          tangents(p,0) = (Tfloat)(fact*u/n);
          tangents(p,1) = (Tfloat)(fact*v/n);
          tangents(p,2) = (Tfloat)(fact*w/n);
        }
      }
      }
      return _draw_spline(points,tangents,W,H,color,opacity,close_set,precision,pattern,init_hatch);
    }

    //! Draw a set of consecutive colored splines in the instance image.
    template<typename tp, typename tt, typename tc>
    CImg<T>& draw_spline(const CImgList<tp>& points, const CImgList<tt>& tangents,
                         const tc *const color, const float opacity=1,
                         const bool close_set=false, const float precision=4,
                         const unsigned int pattern=~0U, const bool init_hatch=true) {
      unsigned int H = ~0U; cimglist_for(points,p) H = cimg::min(H,(unsigned int)(points[p].size()),(unsigned int)(tangents[p].size()));
      return _draw_spline(points,tangents,color,opacity,close_set,precision,pattern,init_hatch,points.size,H);
    }

    //! Draw a set of consecutive colored splines in the instance image.
    template<typename tp, typename tt, typename tc>
    CImg<T>& draw_spline(const CImgList<tp>& points, const CImgList<tt>& tangents,
                         const CImg<tc>& color, const float opacity=1,
                         const bool close_set=false, const float precision=4,
                         const unsigned int pattern=~0U, const bool init_hatch=true) {
      return draw_spline(points,tangents,color.data,opacity,close_set,precision,pattern,init_hatch);
    }

    //! Draw a set of consecutive colored splines in the instance image.
    template<typename tp, typename tt, typename tc>
    CImg<T>& draw_spline(const CImg<tp>& points, const CImg<tt>& tangents,
                         const tc *const color, const float opacity=1,
                         const bool close_set=false, const float precision=4,
                         const unsigned int pattern=~0U, const bool init_hatch=true) {
      return _draw_spline(points,tangents,color,opacity,close_set,precision,pattern,init_hatch,points.width,points.height);
    }

    //! Draw a set of consecutive colored splines in the instance image.
    template<typename tp, typename tt, typename tc>
    CImg<T>& draw_spline(const CImg<tp>& points, const CImg<tt>& tangents,
                         const CImg<tc>& color, const float opacity=1,
                         const bool close_set=false, const float precision=4,
                         const unsigned int pattern=~0U, const bool init_hatch=true) {
      return draw_spline(points,tangents,color.data,opacity,close_set,precision,pattern,init_hatch);
    }

    //! Draw a set of consecutive colored splines in the instance image.
    template<typename t, typename tc>
    CImg<T>& draw_spline(const CImgList<t>& points,
                         const tc *const color, const float opacity=1,
                         const bool close_set=false, const float precision=4,
                         const unsigned int pattern=~0U, const bool init_hatch=true) {
      unsigned int H = ~0U;
      cimglist_for(points,p) { const unsigned int s = points[p].size(); if (s<H) H = s; }
      return _draw_spline(points,color,opacity,close_set,precision,pattern,init_hatch,points.size,H);
    }

    //! Draw a set of consecutive colored splines in the instance image.
    template<typename t, typename tc>
    CImg<T>& draw_spline(const CImgList<t>& points,
                         CImg<tc>& color, const float opacity=1,
                         const bool close_set=false, const float precision=4,
                         const unsigned int pattern=~0U, const bool init_hatch=true) {
      return draw_spline(points,color.data,opacity,close_set,precision,pattern,init_hatch);
    }

    //! Draw a set of consecutive colored lines in the instance image.
    template<typename t, typename tc>
    CImg<T>& draw_spline(const CImg<t>& points,
                         const tc *const color, const float opacity=1,
                         const bool close_set=false, const float precision=4,
                         const unsigned int pattern=~0U, const bool init_hatch=true) {
      return _draw_spline(points,color,opacity,close_set,precision,pattern,init_hatch,points.width,points.height);
    }

    //! Draw a set of consecutive colored lines in the instance image.
    template<typename t, typename tc>
    CImg<T>& draw_spline(const CImg<t>& points,
                         const CImg<tc>& color, const float opacity=1,
                         const bool close_set=false, const float precision=4,
                         const unsigned int pattern=~0U, const bool init_hatch=true) {
      return draw_spline(points,color.data,opacity,close_set,precision,pattern,init_hatch);
    }

    //! Draw a colored arrow in the instance image.
    /**
       \param x0 X-coordinate of the starting arrow point (tail).
       \param y0 Y-coordinate of the starting arrow point (tail).
       \param x1 X-coordinate of the ending arrow point (head).
       \param y1 Y-coordinate of the ending arrow point (head).
       \param color Pointer to \c dimv() consecutive values of type \c T, defining the drawing color.
       \param angle Aperture angle of the arrow head (optional).
       \param length Length of the arrow head. If negative, describes a percentage of the arrow length (optional).
       \param opacity Drawing opacity (optional).
       \param pattern An integer whose bits describe the line pattern (optional).
       \note
       - Clipping is supported.
    **/
    template<typename tc>
    CImg<T>& draw_arrow(const int x0, const int y0,
                        const int x1, const int y1,
                        const tc *const color, const float opacity=1,
                        const float angle=30, const float length=-10,
                        const unsigned int pattern=~0U) {
      if (is_empty()) return *this;
      const float u = (float)(x0 - x1), v = (float)(y0 - y1), sq = u*u + v*v,
        deg = (float)(angle*cimg::valuePI/180), ang = (sq>0)?(float)cimg_std::atan2(v,u):0.0f,
        l = (length>=0)?length:-length*(float)cimg_std::sqrt(sq)/100;
      if (sq>0) {
        const float
            cl = (float)cimg_std::cos(ang - deg), sl = (float)cimg_std::sin(ang - deg),
            cr = (float)cimg_std::cos(ang + deg), sr = (float)cimg_std::sin(ang + deg);
        const int
          xl = x1 + (int)(l*cl), yl = y1 + (int)(l*sl),
          xr = x1 + (int)(l*cr), yr = y1 + (int)(l*sr),
          xc = x1 + (int)((l+1)*(cl+cr))/2, yc = y1 + (int)((l+1)*(sl+sr))/2;
        draw_line(x0,y0,xc,yc,color,opacity,pattern).draw_triangle(x1,y1,xl,yl,xr,yr,color,opacity);
      } else draw_point(x0,y0,color,opacity);
      return *this;
    }

    //! Draw a colored arrow in the instance image.
    template<typename tc>
    CImg<T>& draw_arrow(const int x0, const int y0,
                        const int x1, const int y1,
                        const CImg<tc>& color, const float opacity=1,
                        const float angle=30, const float length=-10,
                        const unsigned int pattern=~0U) {
      return draw_arrow(x0,y0,x1,y1,color.data,opacity,angle,length,pattern);
    }

    //! Draw an image.
    /**
       \param sprite Sprite image.
       \param x0 X-coordinate of the sprite position.
       \param y0 Y-coordinate of the sprite position.
       \param z0 Z-coordinate of the sprite position.
       \param v0 V-coordinate of the sprite position.
       \param opacity Drawing opacity (optional).
       \note
       - Clipping is supported.
    **/
    template<typename t>
    CImg<T>& draw_image(const int x0, const int y0, const int z0, const int v0,
                        const CImg<t>& sprite, const float opacity=1) {
      if (is_empty()) return *this;
      if (!sprite)
        throw CImgArgumentException("CImg<%s>::draw_image() : Specified sprite image (%u,%u,%u,%u,%p) is empty.",
                                    pixel_type(),sprite.width,sprite.height,sprite.depth,sprite.dim,sprite.data);
      if (is_overlapped(sprite)) return draw_image(x0,y0,z0,v0,+sprite,opacity);
      const bool bx = (x0<0), by = (y0<0), bz = (z0<0), bv = (v0<0);
      const int
        lX = sprite.dimx() - (x0 + sprite.dimx()>dimx()?x0 + sprite.dimx() - dimx():0) + (bx?x0:0),
        lY = sprite.dimy() - (y0 + sprite.dimy()>dimy()?y0 + sprite.dimy() - dimy():0) + (by?y0:0),
        lZ = sprite.dimz() - (z0 + sprite.dimz()>dimz()?z0 + sprite.dimz() - dimz():0) + (bz?z0:0),
        lV = sprite.dimv() - (v0 + sprite.dimv()>dimv()?v0 + sprite.dimv() - dimv():0) + (bv?v0:0);
      const t
        *ptrs = sprite.data -
        (bx?x0:0) -
        (by?y0*sprite.dimx():0) -
        (bz?z0*sprite.dimx()*sprite.dimy():0) -
        (bv?v0*sprite.dimx()*sprite.dimy()*sprite.dimz():0);
      const unsigned int
        offX = width - lX,                soffX = sprite.width - lX,
        offY = width*(height - lY),       soffY = sprite.width*(sprite.height - lY),
        offZ = width*height*(depth - lZ), soffZ = sprite.width*sprite.height*(sprite.depth - lZ);
      const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
      if (lX>0 && lY>0 && lZ>0 && lV>0) {
        T *ptrd = ptr(x0<0?0:x0,y0<0?0:y0,z0<0?0:z0,v0<0?0:v0);
        for (int v = 0; v<lV; ++v) {
          for (int z = 0; z<lZ; ++z) {
            for (int y = 0; y<lY; ++y) {
              if (opacity>=1) for (int x = 0; x<lX; ++x) *(ptrd++) = (T)*(ptrs++);
              else for (int x = 0; x<lX; ++x) { *ptrd = (T)(nopacity*(*(ptrs++)) + *ptrd*copacity); ++ptrd; }
              ptrd+=offX; ptrs+=soffX;
            }
            ptrd+=offY; ptrs+=soffY;
          }
          ptrd+=offZ; ptrs+=soffZ;
        }
      }
      return *this;
    }

#ifndef cimg_use_visualcpp6
    // Otimized version (internal).
    CImg<T>& draw_image(const int x0, const int y0, const int z0, const int v0,
                        const CImg<T>& sprite, const float opacity=1) {
      if (is_empty()) return *this;
      if (!sprite)
        throw CImgArgumentException("CImg<%s>::draw_image() : Specified sprite image (%u,%u,%u,%u,%p) is empty.",
                                    pixel_type(),sprite.width,sprite.height,sprite.depth,sprite.dim,sprite.data);
      if (is_overlapped(sprite)) return draw_image(x0,y0,z0,v0,+sprite,opacity);
      const bool bx = (x0<0), by = (y0<0), bz = (z0<0), bv = (v0<0);
      const int
        lX = sprite.dimx() - (x0 + sprite.dimx()>dimx()?x0 + sprite.dimx() - dimx():0) + (bx?x0:0),
        lY = sprite.dimy() - (y0 + sprite.dimy()>dimy()?y0 + sprite.dimy() - dimy():0) + (by?y0:0),
        lZ = sprite.dimz() - (z0 + sprite.dimz()>dimz()?z0 + sprite.dimz() - dimz():0) + (bz?z0:0),
        lV = sprite.dimv() - (v0 + sprite.dimv()>dimv()?v0 + sprite.dimv() - dimv():0) + (bv?v0:0);
      const T
        *ptrs = sprite.data -
        (bx?x0:0) -
        (by?y0*sprite.dimx():0) -
        (bz?z0*sprite.dimx()*sprite.dimy():0) -
        (bv?v0*sprite.dimx()*sprite.dimy()*sprite.dimz():0);
      const unsigned int
        offX = width - lX,                soffX = sprite.width - lX,
        offY = width*(height - lY),       soffY = sprite.width*(sprite.height - lY),
        offZ = width*height*(depth - lZ), soffZ = sprite.width*sprite.height*(sprite.depth - lZ),
        slX = lX*sizeof(T);
      const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
      if (lX>0 && lY>0 && lZ>0 && lV>0) {
        T *ptrd = ptr(x0<0?0:x0,y0<0?0:y0,z0<0?0:z0,v0<0?0:v0);
        for (int v = 0; v<lV; ++v) {
          for (int z = 0; z<lZ; ++z) {
            if (opacity>=1) for (int y = 0; y<lY; ++y) { cimg_std::memcpy(ptrd,ptrs,slX); ptrd+=width; ptrs+=sprite.width; }
            else for (int y = 0; y<lY; ++y) {
              for (int x = 0; x<lX; ++x) { *ptrd = (T)(nopacity*(*(ptrs++)) + *ptrd*copacity); ++ptrd; }
              ptrd+=offX; ptrs+=soffX;
            }
            ptrd+=offY; ptrs+=soffY;
          }
          ptrd+=offZ; ptrs+=soffZ;
        }
      }
      return *this;
    }
#endif

    //! Draw an image.
    template<typename t>
    CImg<T>& draw_image(const int x0, const int y0, const int z0,
                        const CImg<t>& sprite, const float opacity=1) {
      return draw_image(x0,y0,z0,0,sprite,opacity);
    }

    //! Draw an image.
    template<typename t>
    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);
    }

    //! Draw an image.
    template<typename t>
    CImg<T>& draw_image(const int x0,
                        const CImg<t>& sprite, const float opacity=1) {
      return draw_image(x0,0,sprite,opacity);
    }

    //! Draw an image.
    template<typename t>
    CImg<T>& draw_image(const CImg<t>& sprite, const float opacity=1) {
      return draw_image(0,sprite,opacity);
    }

    //! Draw a sprite image in the instance image (masked version).
    /**
       \param sprite Sprite image.
       \param mask Mask image.
       \param x0 X-coordinate of the sprite position in the instance image.
       \param y0 Y-coordinate of the sprite position in the instance image.
       \param z0 Z-coordinate of the sprite position in the instance image.
       \param v0 V-coordinate of the sprite position in the instance image.
       \param mask_valmax Maximum pixel value of the mask image \c mask (optional).
       \param opacity Drawing opacity.
       \note
       - Pixel values of \c mask set the opacity of the corresponding pixels in \c sprite.
       - Clipping is supported.
       - Dimensions along x,y and z of \p sprite and \p mask must be the same.
    **/
    template<typename ti, typename tm>
    CImg<T>& draw_image(const int x0, const int y0, const int z0, const int v0,
                        const CImg<ti>& sprite, const CImg<tm>& mask, const float opacity=1,
                        const float mask_valmax=1) {
      if (is_empty()) return *this;
      if (!sprite)
        throw CImgArgumentException("CImg<%s>::draw_image() : Specified sprite image (%u,%u,%u,%u,%p) is empty.",
                                    pixel_type(),sprite.width,sprite.height,sprite.depth,sprite.dim,sprite.data);
      if (!mask)
        throw CImgArgumentException("CImg<%s>::draw_image() : Specified mask image (%u,%u,%u,%u,%p) is empty.",
                                    pixel_type(),mask.width,mask.height,mask.depth,mask.dim,mask.data);
      if (is_overlapped(sprite)) return draw_image(x0,y0,z0,v0,+sprite,mask,opacity,mask_valmax);
      if (is_overlapped(mask))   return draw_image(x0,y0,z0,v0,sprite,+mask,opacity,mask_valmax);
      if (mask.width!=sprite.width || mask.height!=sprite.height || mask.depth!=sprite.depth)
        throw CImgArgumentException("CImg<%s>::draw_image() : Mask dimension is (%u,%u,%u,%u), while sprite is (%u,%u,%u,%u)",
                                    pixel_type(),mask.width,mask.height,mask.depth,mask.dim,sprite.width,sprite.height,sprite.depth,sprite.dim);
      const bool bx = (x0<0), by = (y0<0), bz = (z0<0), bv = (v0<0);
      const int
        lX = sprite.dimx() - (x0 + sprite.dimx()>dimx()?x0 + sprite.dimx() - dimx():0) + (bx?x0:0),
        lY = sprite.dimy() - (y0 + sprite.dimy()>dimy()?y0 + sprite.dimy() - dimy():0) + (by?y0:0),
        lZ = sprite.dimz() - (z0 + sprite.dimz()>dimz()?z0 + sprite.dimz() - dimz():0) + (bz?z0:0),
        lV = sprite.dimv() - (v0 + sprite.dimv()>dimv()?v0 + sprite.dimv() - dimv():0) + (bv?v0:0);
      const int
        coff = -(bx?x0:0)-(by?y0*mask.dimx():0)-(bz?z0*mask.dimx()*mask.dimy():0)-(bv?v0*mask.dimx()*mask.dimy()*mask.dimz():0),
        ssize = mask.dimx()*mask.dimy()*mask.dimz();
      const ti *ptrs = sprite.data + coff;
      const tm *ptrm = mask.data   + coff;
      const unsigned int
        offX = width - lX,                soffX = sprite.width - lX,
        offY = width*(height - lY),       soffY = sprite.width*(sprite.height - lY),
        offZ = width*height*(depth - lZ), soffZ = sprite.width*sprite.height*(sprite.depth - lZ);
      if (lX>0 && lY>0 && lZ>0 && lV>0) {
        T *ptrd = ptr(x0<0?0:x0,y0<0?0:y0,z0<0?0:z0,v0<0?0:v0);
        for (int v = 0; v<lV; ++v) {
          ptrm = mask.data + (ptrm - mask.data)%ssize;
          for (int z = 0; z<lZ; ++z) {
            for (int y = 0; y<lY; ++y) {
              for (int x=0; x<lX; ++x) {
                const float mopacity = (float)(*(ptrm++)*opacity),
                  nopacity = cimg::abs(mopacity), copacity = mask_valmax - cimg::max(mopacity,0);
                *ptrd = (T)((nopacity*(*(ptrs++)) + *ptrd*copacity)/mask_valmax);
                ++ptrd;
              }
              ptrd+=offX; ptrs+=soffX; ptrm+=soffX;
            }
            ptrd+=offY; ptrs+=soffY; ptrm+=soffY;
          }
          ptrd+=offZ; ptrs+=soffZ; ptrm+=soffZ;
        }
      }
      return *this;
    }

    //! Draw an image.
    template<typename ti, typename tm>
    CImg<T>& draw_image(const int x0, const int y0, const int z0,
                        const CImg<ti>& sprite, const CImg<tm>& mask, const float opacity=1,
                        const float mask_valmax=1) {
      return draw_image(x0,y0,z0,0,sprite,mask,opacity,mask_valmax);
    }

    //! Draw an image.
    template<typename ti, typename tm>
    CImg<T>& draw_image(const int x0, const int y0,
                        const CImg<ti>& sprite, const CImg<tm>& mask, const float opacity=1,
                        const float mask_valmax=1) {
      return draw_image(x0,y0,0,sprite,mask,opacity,mask_valmax);
    }

    //! Draw an image.
    template<typename ti, typename tm>
    CImg<T>& draw_image(const int x0,
                        const CImg<ti>& sprite, const CImg<tm>& mask, const float opacity=1,
                        const float mask_valmax=1) {
      return draw_image(x0,0,sprite,mask,opacity,mask_valmax);
    }

    //! Draw an image.
    template<typename ti, typename tm>
    CImg<T>& draw_image(const CImg<ti>& sprite, const CImg<tm>& mask, const float opacity=1,
                        const float mask_valmax=1) {
      return draw_image(0,sprite,mask,opacity,mask_valmax);
    }

    //! Draw a 4D filled rectangle in the instance image, at coordinates (\c x0,\c y0,\c z0,\c v0)-(\c x1,\c y1,\c z1,\c v1).
    /**
       \param x0 X-coordinate of the upper-left rectangle corner.
       \param y0 Y-coordinate of the upper-left rectangle corner.
       \param z0 Z-coordinate of the upper-left rectangle corner.
       \param v0 V-coordinate of the upper-left rectangle corner.
       \param x1 X-coordinate of the lower-right rectangle corner.
       \param y1 Y-coordinate of the lower-right rectangle corner.
       \param z1 Z-coordinate of the lower-right rectangle corner.
       \param v1 V-coordinate of the lower-right rectangle corner.
       \param val Scalar value used to fill the rectangle area.
       \param opacity Drawing opacity (optional).
       \note
       - Clipping is supported.
    **/
    CImg<T>& draw_rectangle(const int x0, const int y0, const int z0, const int v0,
                            const int x1, const int y1, const int z1, const int v1,
                            const T val, const float opacity=1) {
      if (is_empty()) return *this;
      const bool bx = (x0<x1), by = (y0<y1), bz = (z0<z1), bv = (v0<v1);
      const int
        nx0 = bx?x0:x1, nx1 = bx?x1:x0,
        ny0 = by?y0:y1, ny1 = by?y1:y0,
        nz0 = bz?z0:z1, nz1 = bz?z1:z0,
        nv0 = bv?v0:v1, nv1 = bv?v1:v0;
      const int
        lX = (1 + nx1 - nx0) + (nx1>=dimx()?dimx() - 1 - nx1:0) + (nx0<0?nx0:0),
        lY = (1 + ny1 - ny0) + (ny1>=dimy()?dimy() - 1 - ny1:0) + (ny0<0?ny0:0),
        lZ = (1 + nz1 - nz0) + (nz1>=dimz()?dimz() - 1 - nz1:0) + (nz0<0?nz0:0),
        lV = (1 + nv1 - nv0) + (nv1>=dimv()?dimv() - 1 - nv1:0) + (nv0<0?nv0:0);
      const unsigned int offX = width - lX, offY = width*(height - lY), offZ = width*height*(depth - lZ);
      const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
      T *ptrd = ptr(nx0<0?0:nx0,ny0<0?0:ny0,nz0<0?0:nz0,nv0<0?0:nv0);
      if (lX>0 && lY>0 && lZ>0 && lV>0)
        for (int v = 0; v<lV; ++v) {
          for (int z = 0; z<lZ; ++z) {
            for (int y = 0; y<lY; ++y) {
              if (opacity>=1) {
                if (sizeof(T)!=1) { for (int x = 0; x<lX; ++x) *(ptrd++) = val; ptrd+=offX; }
                else { cimg_std::memset(ptrd,(int)val,lX); ptrd+=width; }
              } else { for (int x = 0; x<lX; ++x) { *ptrd = (T)(nopacity*val + *ptrd*copacity); ++ptrd; } ptrd+=offX; }
            }
            ptrd+=offY;
          }
          ptrd+=offZ;
        }
      return *this;
    }

    //! Draw a 3D filled colored rectangle in the instance image, at coordinates (\c x0,\c y0,\c z0)-(\c x1,\c y1,\c z1).
    /**
       \param x0 X-coordinate of the upper-left rectangle corner.
       \param y0 Y-coordinate of the upper-left rectangle corner.
       \param z0 Z-coordinate of the upper-left rectangle corner.
       \param x1 X-coordinate of the lower-right rectangle corner.
       \param y1 Y-coordinate of the lower-right rectangle corner.
       \param z1 Z-coordinate of the lower-right rectangle corner.
       \param color Pointer to \c dimv() consecutive values of type \c T, defining the drawing color.
       \param opacity Drawing opacity (optional).
       \note
       - Clipping is supported.
    **/
    template<typename tc>
    CImg<T>& draw_rectangle(const int x0, const int y0, const int z0,
                            const int x1, const int y1, const int z1,
                            const tc *const color, const float opacity=1) {
      if (!color)
        throw CImgArgumentException("CImg<%s>::draw_rectangle : specified color is (null)",
                                    pixel_type());
      cimg_forV(*this,k) draw_rectangle(x0,y0,z0,k,x1,y1,z1,k,color[k],opacity);
      return *this;
    }

    //! Draw a 3D filled colored rectangle in the instance image, at coordinates (\c x0,\c y0,\c z0)-(\c x1,\c y1,\c z1).
    template<typename tc>
    CImg<T>& draw_rectangle(const int x0, const int y0, const int z0,
                            const int x1, const int y1, const int z1,
                            const CImg<tc>& color, const float opacity=1) {
      return draw_rectangle(x0,y0,z0,x1,y1,z1,color.data,opacity);
    }

    //! Draw a 3D outlined colored rectangle in the instance image.
    template<typename tc>
    CImg<T>& draw_rectangle(const int x0, const int y0, const int z0,
                            const int x1, const int y1, const int z1,
                            const tc *const color, const float opacity,
                            const unsigned int pattern) {
      return draw_line(x0,y0,z0,x1,y0,z0,color,opacity,pattern,true).
        draw_line(x1,y0,z0,x1,y1,z0,color,opacity,pattern,false).
        draw_line(x1,y1,z0,x0,y1,z0,color,opacity,pattern,false).
        draw_line(x0,y1,z0,x0,y0,z0,color,opacity,pattern,false).
        draw_line(x0,y0,z1,x1,y0,z1,color,opacity,pattern,true).
        draw_line(x1,y0,z1,x1,y1,z1,color,opacity,pattern,false).
        draw_line(x1,y1,z1,x0,y1,z1,color,opacity,pattern,false).
        draw_line(x0,y1,z1,x0,y0,z1,color,opacity,pattern,false).
        draw_line(x0,y0,z0,x0,y0,z1,color,opacity,pattern,true).
        draw_line(x1,y0,z0,x1,y0,z1,color,opacity,pattern,true).
        draw_line(x1,y1,z0,x1,y1,z1,color,opacity,pattern,true).
        draw_line(x0,y1,z0,x0,y1,z1,color,opacity,pattern,true);
    }

    //! Draw a 3D outlined colored rectangle in the instance image.
    template<typename tc>
    CImg<T>& draw_rectangle(const int x0, const int y0, const int z0,
                            const int x1, const int y1, const int z1,
                            const CImg<tc>& color, const float opacity,
                            const unsigned int pattern) {
      return draw_rectangle(x0,y0,z0,x1,y1,z1,color.data,opacity,pattern);
    }

    //! Draw a 2D filled colored rectangle in the instance image, at coordinates (\c x0,\c y0)-(\c x1,\c y1).
    /**
       \param x0 X-coordinate of the upper-left rectangle corner.
       \param y0 Y-coordinate of the upper-left rectangle corner.
       \param x1 X-coordinate of the lower-right rectangle corner.
       \param y1 Y-coordinate of the lower-right rectangle corner.
       \param color Pointer to \c dimv() consecutive values of type \c T, defining the drawing color.
       \param opacity Drawing opacity (optional).
       \note
       - Clipping is supported.
    **/
    template<typename tc>
    CImg<T>& draw_rectangle(const int x0, const int y0,
                            const int x1, const int y1,
                            const tc *const color, const float opacity=1) {
      return draw_rectangle(x0,y0,0,x1,y1,depth-1,color,opacity);
    }

    //! Draw a 2D filled colored rectangle in the instance image, at coordinates (\c x0,\c y0)-(\c x1,\c y1).
    template<typename tc>
    CImg<T>& draw_rectangle(const int x0, const int y0,
                            const int x1, const int y1,
                            const CImg<tc>& color, const float opacity=1) {
      return draw_rectangle(x0,y0,x1,y1,color.data,opacity);
    }

    //! Draw a 2D outlined colored rectangle.
    template<typename tc>
    CImg<T>& draw_rectangle(const int x0, const int y0,
                            const int x1, const int y1,
                            const tc *const color, const float opacity,
                            const unsigned int pattern) {
      if (is_empty()) return *this;
      if (y0==y1) return draw_line(x0,y0,x1,y0,color,opacity,pattern,true);
      if (x0==x1) return draw_line(x0,y0,x0,y1,color,opacity,pattern,true);
      const bool bx = (x0<x1), by = (y0<y1);
      const int
        nx0 = bx?x0:x1, nx1 = bx?x1:x0,
        ny0 = by?y0:y1, ny1 = by?y1:y0;
      if (ny1==ny0+1) return draw_line(nx0,ny0,nx1,ny0,color,opacity,pattern,true).
                      draw_line(nx1,ny1,nx0,ny1,color,opacity,pattern,false);
      return draw_line(nx0,ny0,nx1,ny0,color,opacity,pattern,true).
        draw_line(nx1,ny0+1,nx1,ny1-1,color,opacity,pattern,false).
        draw_line(nx1,ny1,nx0,ny1,color,opacity,pattern,false).
        draw_line(nx0,ny1-1,nx0,ny0+1,color,opacity,pattern,false);
    }

    //! Draw a 2D outlined colored rectangle.
    template<typename tc>
    CImg<T>& draw_rectangle(const int x0, const int y0,
                            const int x1, const int y1,
                            const CImg<tc>& color, const float opacity,
                            const unsigned int pattern) {
      return draw_rectangle(x0,y0,x1,y1,color.data,opacity,pattern);
    }

    // Inner macro for drawing triangles.
#define _cimg_for_triangle1(img,xl,xr,y,x0,y0,x1,y1,x2,y2) \
        for (int y = y0<0?0:y0, \
               xr = y0>=0?x0:(x0-y0*(x2-x0)/(y2-y0)), \
               xl = y1>=0?(y0>=0?(y0==y1?x1:x0):(x0-y0*(x1-x0)/(y1-y0))):(x1-y1*(x2-x1)/(y2-y1)), \
               _sxn=1, \
               _sxr=1, \
               _sxl=1, \
               _dxn = x2>x1?x2-x1:(_sxn=-1,x1-x2), \
               _dxr = x2>x0?x2-x0:(_sxr=-1,x0-x2), \
               _dxl = x1>x0?x1-x0:(_sxl=-1,x0-x1), \
               _dyn = y2-y1, \
               _dyr = y2-y0, \
               _dyl = y1-y0, \
               _counter = (_dxn-=_dyn?_dyn*(_dxn/_dyn):0, \
                           _dxr-=_dyr?_dyr*(_dxr/_dyr):0, \
                           _dxl-=_dyl?_dyl*(_dxl/_dyl):0, \
                           cimg::min((int)(img).height-y-1,y2-y)), \
               _errn = _dyn/2, \
               _errr = _dyr/2, \
               _errl = _dyl/2, \
               _rxn = _dyn?(x2-x1)/_dyn:0, \
               _rxr = _dyr?(x2-x0)/_dyr:0, \
               _rxl = (y0!=y1 && y1>0)?(_dyl?(x1-x0)/_dyl:0): \
                                       (_errl=_errn, _dxl=_dxn, _dyl=_dyn, _sxl=_sxn, _rxn); \
             _counter>=0; --_counter, ++y, \
               xr+=_rxr+((_errr-=_dxr)<0?_errr+=_dyr,_sxr:0), \
               xl+=(y!=y1)?_rxl+((_errl-=_dxl)<0?(_errl+=_dyl,_sxl):0): \
                           (_errl=_errn, _dxl=_dxn, _dyl=_dyn, _sxl=_sxn, _rxl=_rxn, x1-xl))

#define _cimg_for_triangle2(img,xl,cl,xr,cr,y,x0,y0,c0,x1,y1,c1,x2,y2,c2) \
        for (int y = y0<0?0:y0, \
               xr = y0>=0?x0:(x0-y0*(x2-x0)/(y2-y0)), \
               cr = y0>=0?c0:(c0-y0*(c2-c0)/(y2-y0)), \
               xl = y1>=0?(y0>=0?(y0==y1?x1:x0):(x0-y0*(x1-x0)/(y1-y0))):(x1-y1*(x2-x1)/(y2-y1)), \
               cl = y1>=0?(y0>=0?(y0==y1?c1:c0):(c0-y0*(c1-c0)/(y1-y0))):(c1-y1*(c2-c1)/(y2-y1)), \
               _sxn=1, _scn=1, \
               _sxr=1, _scr=1, \
               _sxl=1, _scl=1, \
               _dxn = x2>x1?x2-x1:(_sxn=-1,x1-x2), \
               _dxr = x2>x0?x2-x0:(_sxr=-1,x0-x2), \
               _dxl = x1>x0?x1-x0:(_sxl=-1,x0-x1), \
               _dcn = c2>c1?c2-c1:(_scn=-1,c1-c2), \
               _dcr = c2>c0?c2-c0:(_scr=-1,c0-c2), \
               _dcl = c1>c0?c1-c0:(_scl=-1,c0-c1), \
               _dyn = y2-y1, \
               _dyr = y2-y0, \
               _dyl = y1-y0, \
               _counter =(_dxn-=_dyn?_dyn*(_dxn/_dyn):0, \
                          _dxr-=_dyr?_dyr*(_dxr/_dyr):0, \
                          _dxl-=_dyl?_dyl*(_dxl/_dyl):0, \
                          _dcn-=_dyn?_dyn*(_dcn/_dyn):0, \
                          _dcr-=_dyr?_dyr*(_dcr/_dyr):0, \
                          _dcl-=_dyl?_dyl*(_dcl/_dyl):0, \
                          cimg::min((int)(img).height-y-1,y2-y)), \
               _errn = _dyn/2, _errcn = _errn, \
               _errr = _dyr/2, _errcr = _errr, \
               _errl = _dyl/2, _errcl = _errl, \
               _rxn = _dyn?(x2-x1)/_dyn:0, \
               _rcn = _dyn?(c2-c1)/_dyn:0, \
               _rxr = _dyr?(x2-x0)/_dyr:0, \
               _rcr = _dyr?(c2-c0)/_dyr:0, \
               _rxl = (y0!=y1 && y1>0)?(_dyl?(x1-x0)/_dyl:0): \
                                       (_errl=_errn, _dxl=_dxn, _dyl=_dyn, _sxl=_sxn, _rxn), \
               _rcl = (y0!=y1 && y1>0)?(_dyl?(c1-c0)/_dyl:0): \
                                       (_errcl=_errcn, _dcl=_dcn, _dyl=_dyn, _scl=_scn, _rcn ); \
             _counter>=0; --_counter, ++y, \
               xr+=_rxr+((_errr-=_dxr)<0?_errr+=_dyr,_sxr:0), \
               cr+=_rcr+((_errcr-=_dcr)<0?_errcr+=_dyr,_scr:0), \
               xl+=(y!=y1)?(cl+=_rcl+((_errcl-=_dcl)<0?(_errcl+=_dyl,_scl):0), \
                           _rxl+((_errl-=_dxl)<0?(_errl+=_dyl,_sxl):0)): \
               (_errcl=_errcn, _dcl=_dcn, _dyl=_dyn, _scl=_scn, _rcl=_rcn, cl=c1, \
                _errl=_errn, _dxl=_dxn, _dyl=_dyn, _sxl=_sxn, _rxl=_rxn, x1-xl))

#define _cimg_for_triangle3(img,xl,txl,tyl,xr,txr,tyr,y,x0,y0,tx0,ty0,x1,y1,tx1,ty1,x2,y2,tx2,ty2) \
        for (int y = y0<0?0:y0, \
               xr = y0>=0?x0:(x0-y0*(x2-x0)/(y2-y0)), \
               txr = y0>=0?tx0:(tx0-y0*(tx2-tx0)/(y2-y0)), \
               tyr = y0>=0?ty0:(ty0-y0*(ty2-ty0)/(y2-y0)), \
               xl = y1>=0?(y0>=0?(y0==y1?x1:x0):(x0-y0*(x1-x0)/(y1-y0))):(x1-y1*(x2-x1)/(y2-y1)), \
               txl = y1>=0?(y0>=0?(y0==y1?tx1:tx0):(tx0-y0*(tx1-tx0)/(y1-y0))):(tx1-y1*(tx2-tx1)/(y2-y1)), \
               tyl = y1>=0?(y0>=0?(y0==y1?ty1:ty0):(ty0-y0*(ty1-ty0)/(y1-y0))):(ty1-y1*(ty2-ty1)/(y2-y1)), \
               _sxn=1, _stxn=1, _styn=1, \
               _sxr=1, _stxr=1, _styr=1, \
               _sxl=1, _stxl=1, _styl=1, \
               _dxn = x2>x1?x2-x1:(_sxn=-1,x1-x2), \
               _dxr = x2>x0?x2-x0:(_sxr=-1,x0-x2), \
               _dxl = x1>x0?x1-x0:(_sxl=-1,x0-x1), \
               _dtxn = tx2>tx1?tx2-tx1:(_stxn=-1,tx1-tx2), \
               _dtxr = tx2>tx0?tx2-tx0:(_stxr=-1,tx0-tx2), \
               _dtxl = tx1>tx0?tx1-tx0:(_stxl=-1,tx0-tx1), \
               _dtyn = ty2>ty1?ty2-ty1:(_styn=-1,ty1-ty2), \
               _dtyr = ty2>ty0?ty2-ty0:(_styr=-1,ty0-ty2), \
               _dtyl = ty1>ty0?ty1-ty0:(_styl=-1,ty0-ty1), \
               _dyn = y2-y1, \
               _dyr = y2-y0, \
               _dyl = y1-y0, \
               _counter =(_dxn-=_dyn?_dyn*(_dxn/_dyn):0, \
                          _dxr-=_dyr?_dyr*(_dxr/_dyr):0, \
                          _dxl-=_dyl?_dyl*(_dxl/_dyl):0, \
                          _dtxn-=_dyn?_dyn*(_dtxn/_dyn):0, \
                          _dtxr-=_dyr?_dyr*(_dtxr/_dyr):0, \
                          _dtxl-=_dyl?_dyl*(_dtxl/_dyl):0, \
                          _dtyn-=_dyn?_dyn*(_dtyn/_dyn):0, \
                          _dtyr-=_dyr?_dyr*(_dtyr/_dyr):0, \
                          _dtyl-=_dyl?_dyl*(_dtyl/_dyl):0, \
                          cimg::min((int)(img).height-y-1,y2-y)), \
               _errn = _dyn/2, _errtxn = _errn, _errtyn = _errn, \
               _errr = _dyr/2, _errtxr = _errr, _errtyr = _errr, \
               _errl = _dyl/2, _errtxl = _errl, _errtyl = _errl, \
               _rxn = _dyn?(x2-x1)/_dyn:0, \
               _rtxn = _dyn?(tx2-tx1)/_dyn:0, \
               _rtyn = _dyn?(ty2-ty1)/_dyn:0, \
               _rxr = _dyr?(x2-x0)/_dyr:0, \
               _rtxr = _dyr?(tx2-tx0)/_dyr:0, \
               _rtyr = _dyr?(ty2-ty0)/_dyr:0, \
               _rxl = (y0!=y1 && y1>0)?(_dyl?(x1-x0)/_dyl:0): \
                                       (_errl=_errn, _dxl=_dxn, _dyl=_dyn, _sxl=_sxn, _rxn), \
               _rtxl = (y0!=y1 && y1>0)?(_dyl?(tx1-tx0)/_dyl:0): \
                                       (_errtxl=_errtxn, _dtxl=_dtxn, _dyl=_dyn, _stxl=_stxn, _rtxn ), \
               _rtyl = (y0!=y1 && y1>0)?(_dyl?(ty1-ty0)/_dyl:0): \
                                       (_errtyl=_errtyn, _dtyl=_dtyn, _dyl=_dyn, _styl=_styn, _rtyn ); \
             _counter>=0; --_counter, ++y, \
               xr+=_rxr+((_errr-=_dxr)<0?_errr+=_dyr,_sxr:0), \
               txr+=_rtxr+((_errtxr-=_dtxr)<0?_errtxr+=_dyr,_stxr:0), \
               tyr+=_rtyr+((_errtyr-=_dtyr)<0?_errtyr+=_dyr,_styr:0), \
               xl+=(y!=y1)?(txl+=_rtxl+((_errtxl-=_dtxl)<0?(_errtxl+=_dyl,_stxl):0), \
                            tyl+=_rtyl+((_errtyl-=_dtyl)<0?(_errtyl+=_dyl,_styl):0), \
                           _rxl+((_errl-=_dxl)<0?(_errl+=_dyl,_sxl):0)): \
               (_errtxl=_errtxn, _dtxl=_dtxn, _dyl=_dyn, _stxl=_stxn, _rtxl=_rtxn, txl=tx1, \
                _errtyl=_errtyn, _dtyl=_dtyn, _dyl=_dyn, _styl=_styn, _rtyl=_rtyn, tyl=ty1,\
                _errl=_errn, _dxl=_dxn, _dyl=_dyn, _sxl=_sxn, _rxl=_rxn, x1-xl))

#define _cimg_for_triangle4(img,xl,cl,txl,tyl,xr,cr,txr,tyr,y,x0,y0,c0,tx0,ty0,x1,y1,c1,tx1,ty1,x2,y2,c2,tx2,ty2) \
        for (int y = y0<0?0:y0, \
               xr = y0>=0?x0:(x0-y0*(x2-x0)/(y2-y0)), \
               cr = y0>=0?c0:(c0-y0*(c2-c0)/(y2-y0)), \
               txr = y0>=0?tx0:(tx0-y0*(tx2-tx0)/(y2-y0)), \
               tyr = y0>=0?ty0:(ty0-y0*(ty2-ty0)/(y2-y0)), \
               xl = y1>=0?(y0>=0?(y0==y1?x1:x0):(x0-y0*(x1-x0)/(y1-y0))):(x1-y1*(x2-x1)/(y2-y1)), \
               cl = y1>=0?(y0>=0?(y0==y1?c1:c0):(c0-y0*(c1-c0)/(y1-y0))):(c1-y1*(c2-c1)/(y2-y1)), \
               txl = y1>=0?(y0>=0?(y0==y1?tx1:tx0):(tx0-y0*(tx1-tx0)/(y1-y0))):(tx1-y1*(tx2-tx1)/(y2-y1)), \
               tyl = y1>=0?(y0>=0?(y0==y1?ty1:ty0):(ty0-y0*(ty1-ty0)/(y1-y0))):(ty1-y1*(ty2-ty1)/(y2-y1)), \
               _sxn=1, _scn=1, _stxn=1, _styn=1, \
               _sxr=1, _scr=1, _stxr=1, _styr=1, \
               _sxl=1, _scl=1, _stxl=1, _styl=1, \
               _dxn = x2>x1?x2-x1:(_sxn=-1,x1-x2), \
               _dxr = x2>x0?x2-x0:(_sxr=-1,x0-x2), \
               _dxl = x1>x0?x1-x0:(_sxl=-1,x0-x1), \
               _dcn = c2>c1?c2-c1:(_scn=-1,c1-c2), \
               _dcr = c2>c0?c2-c0:(_scr=-1,c0-c2), \
               _dcl = c1>c0?c1-c0:(_scl=-1,c0-c1), \
               _dtxn = tx2>tx1?tx2-tx1:(_stxn=-1,tx1-tx2), \
               _dtxr = tx2>tx0?tx2-tx0:(_stxr=-1,tx0-tx2), \
               _dtxl = tx1>tx0?tx1-tx0:(_stxl=-1,tx0-tx1), \
               _dtyn = ty2>ty1?ty2-ty1:(_styn=-1,ty1-ty2), \
               _dtyr = ty2>ty0?ty2-ty0:(_styr=-1,ty0-ty2), \
               _dtyl = ty1>ty0?ty1-ty0:(_styl=-1,ty0-ty1), \
               _dyn = y2-y1, \
               _dyr = y2-y0, \
               _dyl = y1-y0, \
               _counter =(_dxn-=_dyn?_dyn*(_dxn/_dyn):0, \
                          _dxr-=_dyr?_dyr*(_dxr/_dyr):0, \
                          _dxl-=_dyl?_dyl*(_dxl/_dyl):0, \
                          _dcn-=_dyn?_dyn*(_dcn/_dyn):0, \
                          _dcr-=_dyr?_dyr*(_dcr/_dyr):0, \
                          _dcl-=_dyl?_dyl*(_dcl/_dyl):0, \
                          _dtxn-=_dyn?_dyn*(_dtxn/_dyn):0, \
                          _dtxr-=_dyr?_dyr*(_dtxr/_dyr):0, \
                          _dtxl-=_dyl?_dyl*(_dtxl/_dyl):0, \
                          _dtyn-=_dyn?_dyn*(_dtyn/_dyn):0, \
                          _dtyr-=_dyr?_dyr*(_dtyr/_dyr):0, \
                          _dtyl-=_dyl?_dyl*(_dtyl/_dyl):0, \
                          cimg::min((int)(img).height-y-1,y2-y)), \
               _errn = _dyn/2, _errcn = _errn, _errtxn = _errn, _errtyn = _errn, \
               _errr = _dyr/2, _errcr = _errr, _errtxr = _errr, _errtyr = _errr, \
               _errl = _dyl/2, _errcl = _errl, _errtxl = _errl, _errtyl = _errl, \
               _rxn = _dyn?(x2-x1)/_dyn:0, \
               _rcn = _dyn?(c2-c1)/_dyn:0, \
               _rtxn = _dyn?(tx2-tx1)/_dyn:0, \
               _rtyn = _dyn?(ty2-ty1)/_dyn:0, \
               _rxr = _dyr?(x2-x0)/_dyr:0, \
               _rcr = _dyr?(c2-c0)/_dyr:0, \
               _rtxr = _dyr?(tx2-tx0)/_dyr:0, \
               _rtyr = _dyr?(ty2-ty0)/_dyr:0, \
               _rxl = (y0!=y1 && y1>0)?(_dyl?(x1-x0)/_dyl:0): \
                                       (_errl=_errn, _dxl=_dxn, _dyl=_dyn, _sxl=_sxn, _rxn), \
               _rcl = (y0!=y1 && y1>0)?(_dyl?(c1-c0)/_dyl:0): \
                                       (_errcl=_errcn, _dcl=_dcn, _dyl=_dyn, _scl=_scn, _rcn ), \
               _rtxl = (y0!=y1 && y1>0)?(_dyl?(tx1-tx0)/_dyl:0): \
                                        (_errtxl=_errtxn, _dtxl=_dtxn, _dyl=_dyn, _stxl=_stxn, _rtxn ), \
               _rtyl = (y0!=y1 && y1>0)?(_dyl?(ty1-ty0)/_dyl:0): \
                                        (_errtyl=_errtyn, _dtyl=_dtyn, _dyl=_dyn, _styl=_styn, _rtyn ); \
             _counter>=0; --_counter, ++y, \
               xr+=_rxr+((_errr-=_dxr)<0?_errr+=_dyr,_sxr:0), \
               cr+=_rcr+((_errcr-=_dcr)<0?_errcr+=_dyr,_scr:0), \
               txr+=_rtxr+((_errtxr-=_dtxr)<0?_errtxr+=_dyr,_stxr:0), \
               tyr+=_rtyr+((_errtyr-=_dtyr)<0?_errtyr+=_dyr,_styr:0), \
               xl+=(y!=y1)?(cl+=_rcl+((_errcl-=_dcl)<0?(_errcl+=_dyl,_scl):0), \
                            txl+=_rtxl+((_errtxl-=_dtxl)<0?(_errtxl+=_dyl,_stxl):0), \
                            tyl+=_rtyl+((_errtyl-=_dtyl)<0?(_errtyl+=_dyl,_styl):0), \
                            _rxl+((_errl-=_dxl)<0?(_errl+=_dyl,_sxl):0)): \
               (_errcl=_errcn, _dcl=_dcn, _dyl=_dyn, _scl=_scn, _rcl=_rcn, cl=c1, \
                _errtxl=_errtxn, _dtxl=_dtxn, _dyl=_dyn, _stxl=_stxn, _rtxl=_rtxn, txl=tx1, \
                _errtyl=_errtyn, _dtyl=_dtyn, _dyl=_dyn, _styl=_styn, _rtyl=_rtyn, tyl=ty1, \
                _errl=_errn, _dxl=_dxn, _dyl=_dyn, _sxl=_sxn, _rxl=_rxn, x1-xl))

#define _cimg_for_triangle5(img,xl,txl,tyl,lxl,lyl,xr,txr,tyr,lxr,lyr,y,x0,y0,tx0,ty0,lx0,ly0,x1,y1,tx1,ty1,lx1,ly1,x2,y2,tx2,ty2,lx2,ly2) \
        for (int y = y0<0?0:y0, \
               xr = y0>=0?x0:(x0-y0*(x2-x0)/(y2-y0)), \
               txr = y0>=0?tx0:(tx0-y0*(tx2-tx0)/(y2-y0)), \
               tyr = y0>=0?ty0:(ty0-y0*(ty2-ty0)/(y2-y0)), \
               lxr = y0>=0?lx0:(lx0-y0*(lx2-lx0)/(y2-y0)), \
               lyr = y0>=0?ly0:(ly0-y0*(ly2-ly0)/(y2-y0)), \
               xl = y1>=0?(y0>=0?(y0==y1?x1:x0):(x0-y0*(x1-x0)/(y1-y0))):(x1-y1*(x2-x1)/(y2-y1)), \
               txl = y1>=0?(y0>=0?(y0==y1?tx1:tx0):(tx0-y0*(tx1-tx0)/(y1-y0))):(tx1-y1*(tx2-tx1)/(y2-y1)), \
               tyl = y1>=0?(y0>=0?(y0==y1?ty1:ty0):(ty0-y0*(ty1-ty0)/(y1-y0))):(ty1-y1*(ty2-ty1)/(y2-y1)), \
               lxl = y1>=0?(y0>=0?(y0==y1?lx1:lx0):(lx0-y0*(lx1-lx0)/(y1-y0))):(lx1-y1*(lx2-lx1)/(y2-y1)), \
               lyl = y1>=0?(y0>=0?(y0==y1?ly1:ly0):(ly0-y0*(ly1-ly0)/(y1-y0))):(ly1-y1*(ly2-ly1)/(y2-y1)), \
               _sxn=1, _stxn=1, _styn=1, _slxn=1, _slyn=1, \
               _sxr=1, _stxr=1, _styr=1, _slxr=1, _slyr=1, \
               _sxl=1, _stxl=1, _styl=1, _slxl=1, _slyl=1, \
               _dxn = x2>x1?x2-x1:(_sxn=-1,x1-x2), _dyn = y2-y1, \
               _dxr = x2>x0?x2-x0:(_sxr=-1,x0-x2), _dyr = y2-y0, \
               _dxl = x1>x0?x1-x0:(_sxl=-1,x0-x1), _dyl = y1-y0, \
               _dtxn = tx2>tx1?tx2-tx1:(_stxn=-1,tx1-tx2), \
               _dtxr = tx2>tx0?tx2-tx0:(_stxr=-1,tx0-tx2), \
               _dtxl = tx1>tx0?tx1-tx0:(_stxl=-1,tx0-tx1), \
               _dtyn = ty2>ty1?ty2-ty1:(_styn=-1,ty1-ty2), \
               _dtyr = ty2>ty0?ty2-ty0:(_styr=-1,ty0-ty2), \
               _dtyl = ty1>ty0?ty1-ty0:(_styl=-1,ty0-ty1), \
               _dlxn = lx2>lx1?lx2-lx1:(_slxn=-1,lx1-lx2), \
               _dlxr = lx2>lx0?lx2-lx0:(_slxr=-1,lx0-lx2), \
               _dlxl = lx1>lx0?lx1-lx0:(_slxl=-1,lx0-lx1), \
               _dlyn = ly2>ly1?ly2-ly1:(_slyn=-1,ly1-ly2), \
               _dlyr = ly2>ly0?ly2-ly0:(_slyr=-1,ly0-ly2), \
               _dlyl = ly1>ly0?ly1-ly0:(_slyl=-1,ly0-ly1), \
               _counter =(_dxn-=_dyn?_dyn*(_dxn/_dyn):0, \
                          _dxr-=_dyr?_dyr*(_dxr/_dyr):0, \
                          _dxl-=_dyl?_dyl*(_dxl/_dyl):0, \
                          _dtxn-=_dyn?_dyn*(_dtxn/_dyn):0, \
                          _dtxr-=_dyr?_dyr*(_dtxr/_dyr):0, \
                          _dtxl-=_dyl?_dyl*(_dtxl/_dyl):0, \
                          _dtyn-=_dyn?_dyn*(_dtyn/_dyn):0, \
                          _dtyr-=_dyr?_dyr*(_dtyr/_dyr):0, \
                          _dtyl-=_dyl?_dyl*(_dtyl/_dyl):0, \
                          _dlxn-=_dyn?_dyn*(_dlxn/_dyn):0, \
                          _dlxr-=_dyr?_dyr*(_dlxr/_dyr):0, \
                          _dlxl-=_dyl?_dyl*(_dlxl/_dyl):0, \
                          _dlyn-=_dyn?_dyn*(_dlyn/_dyn):0, \
                          _dlyr-=_dyr?_dyr*(_dlyr/_dyr):0, \
                          _dlyl-=_dyl?_dyl*(_dlyl/_dyl):0, \
                          cimg::min((int)(img).height-y-1,y2-y)), \
               _errn = _dyn/2, _errtxn = _errn, _errtyn = _errn, _errlxn = _errn, _errlyn = _errn, \
               _errr = _dyr/2, _errtxr = _errr, _errtyr = _errr, _errlxr = _errr, _errlyr = _errr, \
               _errl = _dyl/2, _errtxl = _errl, _errtyl = _errl, _errlxl = _errl, _errlyl = _errl, \
               _rxn = _dyn?(x2-x1)/_dyn:0, \
               _rtxn = _dyn?(tx2-tx1)/_dyn:0, \
               _rtyn = _dyn?(ty2-ty1)/_dyn:0, \
               _rlxn = _dyn?(lx2-lx1)/_dyn:0, \
               _rlyn = _dyn?(ly2-ly1)/_dyn:0, \
               _rxr = _dyr?(x2-x0)/_dyr:0, \
               _rtxr = _dyr?(tx2-tx0)/_dyr:0, \
               _rtyr = _dyr?(ty2-ty0)/_dyr:0, \
               _rlxr = _dyr?(lx2-lx0)/_dyr:0, \
               _rlyr = _dyr?(ly2-ly0)/_dyr:0, \
               _rxl = (y0!=y1 && y1>0)?(_dyl?(x1-x0)/_dyl:0): \
                                       (_errl=_errn, _dxl=_dxn, _dyl=_dyn, _sxl=_sxn, _rxn), \
               _rtxl = (y0!=y1 && y1>0)?(_dyl?(tx1-tx0)/_dyl:0): \
                                        (_errtxl=_errtxn, _dtxl=_dtxn, _dyl=_dyn, _stxl=_stxn, _rtxn ), \
               _rtyl = (y0!=y1 && y1>0)?(_dyl?(ty1-ty0)/_dyl:0): \
                                        (_errtyl=_errtyn, _dtyl=_dtyn, _dyl=_dyn, _styl=_styn, _rtyn ), \
               _rlxl = (y0!=y1 && y1>0)?(_dyl?(lx1-lx0)/_dyl:0): \
                                        (_errlxl=_errlxn, _dlxl=_dlxn, _dyl=_dyn, _slxl=_slxn, _rlxn ), \
               _rlyl = (y0!=y1 && y1>0)?(_dyl?(ly1-ly0)/_dyl:0): \
                                        (_errlyl=_errlyn, _dlyl=_dlyn, _dyl=_dyn, _slyl=_slyn, _rlyn ); \
             _counter>=0; --_counter, ++y, \
               xr+=_rxr+((_errr-=_dxr)<0?_errr+=_dyr,_sxr:0), \
               txr+=_rtxr+((_errtxr-=_dtxr)<0?_errtxr+=_dyr,_stxr:0), \
               tyr+=_rtyr+((_errtyr-=_dtyr)<0?_errtyr+=_dyr,_styr:0), \
               lxr+=_rlxr+((_errlxr-=_dlxr)<0?_errlxr+=_dyr,_slxr:0), \
               lyr+=_rlyr+((_errlyr-=_dlyr)<0?_errlyr+=_dyr,_slyr:0), \
               xl+=(y!=y1)?(txl+=_rtxl+((_errtxl-=_dtxl)<0?(_errtxl+=_dyl,_stxl):0), \
                            tyl+=_rtyl+((_errtyl-=_dtyl)<0?(_errtyl+=_dyl,_styl):0), \
                            lxl+=_rlxl+((_errlxl-=_dlxl)<0?(_errlxl+=_dyl,_slxl):0), \
                            lyl+=_rlyl+((_errlyl-=_dlyl)<0?(_errlyl+=_dyl,_slyl):0), \
                            _rxl+((_errl-=_dxl)<0?(_errl+=_dyl,_sxl):0)): \
               (_errtxl=_errtxn, _dtxl=_dtxn, _dyl=_dyn, _stxl=_stxn, _rtxl=_rtxn, txl=tx1, \
                _errtyl=_errtyn, _dtyl=_dtyn, _dyl=_dyn, _styl=_styn, _rtyl=_rtyn, tyl=ty1, \
                _errlxl=_errlxn, _dlxl=_dlxn, _dyl=_dyn, _slxl=_slxn, _rlxl=_rlxn, lxl=lx1, \
                _errlyl=_errlyn, _dlyl=_dlyn, _dyl=_dyn, _slyl=_slyn, _rlyl=_rlyn, lyl=ly1, \
                _errl=_errn, _dxl=_dxn, _dyl=_dyn, _sxl=_sxn, _rxl=_rxn, x1-xl))

    // Draw a colored triangle (inner routine, uses bresenham's algorithm).
    template<typename tc>
    CImg<T>& _draw_triangle(const int x0, const int y0,
                            const int x1, const int y1,
                            const int x2, const int y2,
                            const tc *const color, const float opacity,
                            const float brightness) {
      _draw_scanline(color,opacity);
      const float nbrightness = brightness<0?0:(brightness>2?2:brightness);
      int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2;
      if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1);
      if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2);
      if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2);
      if (ny0<dimy() && ny2>=0) {
        if ((nx1 - nx0)*(ny2 - ny0) - (nx2 - nx0)*(ny1 - ny0)<0)
          _cimg_for_triangle1(*this,xl,xr,y,nx0,ny0,nx1,ny1,nx2,ny2) _draw_scanline(xl,xr,y,color,opacity,nbrightness);
        else
          _cimg_for_triangle1(*this,xl,xr,y,nx0,ny0,nx1,ny1,nx2,ny2) _draw_scanline(xr,xl,y,color,opacity,nbrightness);
      }
      return *this;
    }

    //! Draw a 2D filled colored triangle.
    template<typename tc>
    CImg<T>& draw_triangle(const int x0, const int y0,
                           const int x1, const int y1,
                           const int x2, const int y2,
                           const tc *const color, const float opacity=1) {
      if (is_empty()) return *this;
      if (!color)
        throw CImgArgumentException("CImg<%s>::draw_triangle : Specified color is (null).",
                                    pixel_type());
      _draw_triangle(x0,y0,x1,y1,x2,y2,color,opacity,1);
      return *this;
    }

    //! Draw a 2D filled colored triangle.
    template<typename tc>
    CImg<T>& draw_triangle(const int x0, const int y0,
                           const int x1, const int y1,
                           const int x2, const int y2,
                           const CImg<tc>& color, const float opacity=1) {
      return draw_triangle(x0,y0,x1,y1,x2,y2,color.data,opacity);
    }

    //! Draw a 2D outlined colored triangle.
    template<typename tc>
    CImg<T>& draw_triangle(const int x0, const int y0,
                           const int x1, const int y1,
                           const int x2, const int y2,
                           const tc *const color, const float opacity,
                           const unsigned int pattern) {
      if (is_empty()) return *this;
      if (!color)
        throw CImgArgumentException("CImg<%s>::draw_triangle : Specified color is (null).",
                                    pixel_type());
      draw_line(x0,y0,x1,y1,color,opacity,pattern,true).
        draw_line(x1,y1,x2,y2,color,opacity,pattern,false).
        draw_line(x2,y2,x0,y0,color,opacity,pattern,false);
      return *this;
    }

    //! Draw a 2D outlined colored triangle.
    template<typename tc>
    CImg<T>& draw_triangle(const int x0, const int y0,
                           const int x1, const int y1,
                           const int x2, const int y2,
                           const CImg<tc>& color, const float opacity,
                           const unsigned int pattern) {
      return draw_triangle(x0,y0,x1,y1,x2,y2,color.data,opacity,pattern);
    }

    //! Draw a 2D filled colored triangle, with z-buffering.
    template<typename tc>
    CImg<T>& draw_triangle(float *const zbuffer,
                           const int x0, const int y0, const float z0,
                           const int x1, const int y1, const float z1,
                           const int x2, const int y2, const float z2,
                           const tc *const color, const float opacity=1,
                           const float brightness=1) {
      if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this;
      if (!color)
        throw CImgArgumentException("CImg<%s>::draw_triangle() : Specified color is (null).",
                                    pixel_type());
      static const T maxval = (T)cimg::min(cimg::type<T>::max(),cimg::type<tc>::max());
      const float
        nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0),
        nbrightness = brightness<0?0:(brightness>2?2:brightness);
      const int whz = width*height*depth, offx = dim*whz;
      int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2;
      float nz0 = 1/z0, nz1 = 1/z1, nz2 = 1/z2;
      if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,nz0,nz1);
      if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,nz0,nz2);
      if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,nz1,nz2);
      if (ny0>=dimy() || ny2<0) return *this;
      float
        pzl = (nz1 - nz0)/(ny1 - ny0),
        pzr = (nz2 - nz0)/(ny2 - ny0),
        pzn = (nz2 - nz1)/(ny2 - ny1),
        zr = ny0>=0?nz0:(nz0 - ny0*(nz2 - nz0)/(ny2 - ny0)),
        zl = ny1>=0?(ny0>=0?nz0:(nz0 - ny0*(nz1 - nz0)/(ny1 - ny0))):(pzl=pzn,(nz1 - ny1*(nz2 - nz1)/(ny2 - ny1)));
      _cimg_for_triangle1(*this,xleft0,xright0,y,nx0,ny0,nx1,ny1,nx2,ny2) {
        if (y==ny1) { zl = nz1; pzl = pzn; }
        int xleft = xleft0, xright = xright0;
        float zleft = zl, zright = zr;
        if (xright<xleft) cimg::swap(xleft,xright,zleft,zright);
        const int dx = xright - xleft;
        const float pentez = (zright - zleft)/dx;
        if (xleft<0 && dx) zleft-=xleft*(zright - zleft)/dx;
        if (xleft<0) xleft = 0;
        if (xright>=dimx()-1) xright = dimx()-1;
        T* ptrd = ptr(xleft,y,0,0);
        float *ptrz = zbuffer + xleft + y*width;
        if (opacity>=1) {
          if (nbrightness==1) for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) {
            if (zleft>*ptrz) {
              *ptrz = zleft;
              const tc *col = color; cimg_forV(*this,k) { *ptrd = (T)*(col++); ptrd+=whz; }
              ptrd-=offx;
            }
            zleft+=pentez;
          } else if (nbrightness<1) for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) {
            if (zleft>*ptrz) {
              *ptrz = zleft;
              const tc *col = color; cimg_forV(*this,k) { *ptrd = (T)(nbrightness*(*col++)); ptrd+=whz; }
              ptrd-=offx;
            }
            zleft+=pentez;
          } else for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) {
            if (zleft>*ptrz) {
              *ptrz = zleft;
              const tc *col = color; cimg_forV(*this,k) { *ptrd = (T)((2-nbrightness)**(col++) + (nbrightness-1)*maxval); ptrd+=whz; }
              ptrd-=offx;
            }
            zleft+=pentez;
          }
        } else {
          if (nbrightness==1) for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) {
            if (zleft>*ptrz) {
              *ptrz = zleft;
              const tc *col = color; cimg_forV(*this,k) { *ptrd = (T)(nopacity**(col++) + *ptrd*copacity); ptrd+=whz; }
              ptrd-=offx;
            }
            zleft+=pentez;
          } else if (nbrightness<1) for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) {
            if (zleft>*ptrz) {
              *ptrz = zleft;
              const tc *col = color; cimg_forV(*this,k) { *ptrd = (T)(nopacity*nbrightness**(col++) + *ptrd*copacity); ptrd+=whz; }
              ptrd-=offx;
            }
            zleft+=pentez;
          } else for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) {
            if (zleft>*ptrz) {
              *ptrz = zleft;
              const tc *col = color;
              cimg_forV(*this,k) {
                const T val = (T)((2-nbrightness)**(col++) + (nbrightness-1)*maxval);
                *ptrd = (T)(nopacity*val + *ptrd*copacity);
                ptrd+=whz;
              }
              ptrd-=offx;
            }
            zleft+=pentez;
          }
        }
        zr+=pzr; zl+=pzl;
      }
      return *this;
    }

    //! Draw a 2D filled colored triangle, with z-buffering.
    template<typename tc>
    CImg<T>& draw_triangle(float *const zbuffer,
                           const int x0, const int y0, const float z0,
                           const int x1, const int y1, const float z1,
                           const int x2, const int y2, const float z2,
                           const CImg<tc>& color, const float opacity=1,
                           const float brightness=1) {
      return draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color.data,opacity,brightness);
    }

    //! Draw a 2D Gouraud-shaded colored triangle.
    /**
       \param x0 = X-coordinate of the first corner in the instance image.
       \param y0 = Y-coordinate of the first corner in the instance image.
       \param x1 = X-coordinate of the second corner in the instance image.
       \param y1 = Y-coordinate of the second corner in the instance image.
       \param x2 = X-coordinate of the third corner in the instance image.
       \param y2 = Y-coordinate of the third corner in the instance image.
       \param color = array of dimv() values of type \c T, defining the global drawing color.
       \param brightness0 = brightness of the first corner (in [0,2]).
       \param brightness1 = brightness of the second corner (in [0,2]).
       \param brightness2 = brightness of the third corner (in [0,2]).
       \param opacity = opacity of the drawing.
       \note Clipping is supported.
    **/
    template<typename tc>
    CImg<T>& draw_triangle(const int x0, const int y0,
                           const int x1, const int y1,
                           const int x2, const int y2,
                           const tc *const color,
                           const float brightness0,
                           const float brightness1,
                           const float brightness2,
                           const float opacity=1) {
      if (is_empty()) return *this;
      if (!color)
        throw CImgArgumentException("CImg<%s>::draw_triangle : Specified color is (null).",
                                    pixel_type());
      static const T maxval = (T)cimg::min(cimg::type<T>::max(),cimg::type<tc>::max());
      const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
      const int whz = width*height*depth, offx = dim*whz-1;
      int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2,
        nc0 = (int)((brightness0<0?0:(brightness0>2?2:brightness0))*256),
        nc1 = (int)((brightness1<0?0:(brightness1>2?2:brightness1))*256),
        nc2 = (int)((brightness2<0?0:(brightness2>2?2:brightness2))*256);
      if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,nc0,nc1);
      if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,nc0,nc2);
      if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,nc1,nc2);
      if (ny0>=dimy() || ny2<0) return *this;
      _cimg_for_triangle2(*this,xleft0,cleft0,xright0,cright0,y,nx0,ny0,nc0,nx1,ny1,nc1,nx2,ny2,nc2) {
        int xleft = xleft0, xright = xright0, cleft = cleft0, cright = cright0;
        if (xright<xleft) cimg::swap(xleft,xright,cleft,cright);
        const int
          dx = xright - xleft,
          dc = cright>cleft?cright - cleft:cleft - cright,
          rc = dx?(cright - cleft)/dx:0,
          sc = cright>cleft?1:-1,
          ndc = dc-(dx?dx*(dc/dx):0);
        int errc = dx>>1;
        if (xleft<0 && dx) cleft-=xleft*(cright - cleft)/dx;
        if (xleft<0) xleft = 0;
        if (xright>=dimx()-1) xright = dimx()-1;
        T* ptrd = ptr(xleft,y);
        if (opacity>=1) for (int x = xleft; x<=xright; ++x) {
          const tc *col = color;
          cimg_forV(*this,k) {
            *ptrd = (T)(cleft<256?cleft**(col++)/256:((512-cleft)**(col++)+(cleft-256)*maxval)/256);
            ptrd+=whz;
          }
          ptrd-=offx;
          cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0);
        } else for (int x = xleft; x<=xright; ++x) {
          const tc *col = color;
          cimg_forV(*this,k) {
            const T val = (T)(cleft<256?cleft**(col++)/256:((512-cleft)**(col++)+(cleft-256)*maxval)/256);
            *ptrd = (T)(nopacity*val + *ptrd*copacity);
            ptrd+=whz;
          }
          ptrd-=offx;
          cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0);
        }
      }
      return *this;
    }

    //! Draw a 2D Gouraud-shaded colored triangle.
    template<typename tc>
    CImg<T>& draw_triangle(const int x0, const int y0,
                           const int x1, const int y1,
                           const int x2, const int y2,
                           const CImg<tc>& color,
                           const float brightness0,
                           const float brightness1,
                           const float brightness2,
                           const float opacity=1) {
      return draw_triangle(x0,y0,x1,y1,x2,y2,color.data,brightness0,brightness1,brightness2,opacity);
    }

    //! Draw a 2D Gouraud-shaded colored triangle, with z-buffering.
    template<typename tc>
    CImg<T>& draw_triangle(float *const zbuffer,
                           const int x0, const int y0, const float z0,
                           const int x1, const int y1, const float z1,
                           const int x2, const int y2, const float z2,
                           const tc *const color,
                           const float brightness0,
                           const float brightness1,
                           const float brightness2,
                           const float opacity=1) {
      if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this;
      if (!color)
        throw CImgArgumentException("CImg<%s>::draw_triangle() : Specified color is (null).",
                                    pixel_type());
      static const T maxval = (T)cimg::min(cimg::type<T>::max(),cimg::type<tc>::max());
      const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
      const int whz = width*height*depth, offx = dim*whz;
      int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2,
        nc0 = (int)((brightness0<0?0:(brightness0>2?2:brightness0))*256),
        nc1 = (int)((brightness1<0?0:(brightness1>2?2:brightness1))*256),
        nc2 = (int)((brightness2<0?0:(brightness2>2?2:brightness2))*256);
      float nz0 = 1/z0, nz1 = 1/z1, nz2 = 1/z2;
      if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,nz0,nz1,nc0,nc1);
      if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,nz0,nz2,nc0,nc2);
      if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,nz1,nz2,nc1,nc2);
      if (ny0>=dimy() || ny2<0) return *this;
      float
        pzl = (nz1 - nz0)/(ny1 - ny0),
        pzr = (nz2 - nz0)/(ny2 - ny0),
        pzn = (nz2 - nz1)/(ny2 - ny1),
        zr = ny0>=0?nz0:(nz0 - ny0*(nz2 - nz0)/(ny2 - ny0)),
        zl = ny1>=0?(ny0>=0?nz0:(nz0 - ny0*(nz1 - nz0)/(ny1 - ny0))):(pzl=pzn,(nz1 - ny1*(nz2 - nz1)/(ny2 - ny1)));
      _cimg_for_triangle2(*this,xleft0,cleft0,xright0,cright0,y,nx0,ny0,nc0,nx1,ny1,nc1,nx2,ny2,nc2) {
        if (y==ny1) { zl = nz1; pzl = pzn; }
        int xleft = xleft0, xright = xright0, cleft = cleft0, cright = cright0;
        float zleft = zl, zright = zr;
        if (xright<xleft) cimg::swap(xleft,xright,zleft,zright,cleft,cright);
        const int
          dx = xright - xleft,
          dc = cright>cleft?cright - cleft:cleft - cright,
          rc = dx?(cright-cleft)/dx:0,
          sc = cright>cleft?1:-1,
          ndc = dc-(dx?dx*(dc/dx):0);
        const float pentez = (zright - zleft)/dx;
        int errc = dx>>1;
        if (xleft<0 && dx) {
          cleft-=xleft*(cright - cleft)/dx;
          zleft-=xleft*(zright - zleft)/dx;
        }
        if (xleft<0) xleft = 0;
        if (xright>=dimx()-1) xright = dimx()-1;
        T *ptrd = ptr(xleft,y);
        float *ptrz = zbuffer + xleft + y*width;
        if (opacity>=1) for (int x = xleft; x<=xright; ++x, ++ptrd, ++ptrz) {
          if (zleft>*ptrz) {
            *ptrz = zleft;
            const tc *col = color;
            cimg_forV(*this,k) {
              *ptrd = (T)(cleft<256?cleft**(col++)/256:((512-cleft)**(col++)+(cleft-256)*maxval)/256);
              ptrd+=whz;
            }
            ptrd-=offx;
          }
          zleft+=pentez;
          cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0);
        } else for (int x = xleft; x<=xright; ++x, ++ptrd, ++ptrz) {
          if (zleft>*ptrz) {
            *ptrz = zleft;
            const tc *col = color;
            cimg_forV(*this,k) {
              const T val = (T)(cleft<256?cleft**(col++)/256:((512-cleft)**(col++)+(cleft-256)*maxval)/256);
              *ptrd = (T)(nopacity*val + *ptrd*copacity);
              ptrd+=whz;
            }
            ptrd-=offx;
          }
          zleft+=pentez;
          cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0);
        }
        zr+=pzr; zl+=pzl;
      }
      return *this;
    }

    //! Draw a Gouraud triangle with z-buffer consideration.
    template<typename tc>
    CImg<T>& draw_triangle(float *const zbuffer,
                           const int x0, const int y0, const float z0,
                           const int x1, const int y1, const float z1,
                           const int x2, const int y2, const float z2,
                           const CImg<tc>& color,
                           const float brightness0,
                           const float brightness1,
                           const float brightness2,
                           const float opacity=1) {
      return draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color.data,brightness0,brightness1,brightness2,opacity);
    }

    //! Draw a 2D textured triangle.
    /**
       \param x0 = X-coordinate of the first corner in the instance image.
       \param y0 = Y-coordinate of the first corner in the instance image.
       \param x1 = X-coordinate of the second corner in the instance image.
       \param y1 = Y-coordinate of the second corner in the instance image.
       \param x2 = X-coordinate of the third corner in the instance image.
       \param y2 = Y-coordinate of the third corner in the instance image.
       \param texture = texture image used to fill the triangle.
       \param tx0 = X-coordinate of the first corner in the texture image.
       \param ty0 = Y-coordinate of the first corner in the texture image.
       \param tx1 = X-coordinate of the second corner in the texture image.
       \param ty1 = Y-coordinate of the second corner in the texture image.
       \param tx2 = X-coordinate of the third corner in the texture image.
       \param ty2 = Y-coordinate of the third corner in the texture image.
       \param opacity = opacity of the drawing.
       \param brightness = brightness of the drawing (in [0,2]).
       \note Clipping is supported, but texture coordinates do not support clipping.
    **/
    template<typename tc>
    CImg<T>& draw_triangle(const int x0, const int y0,
                           const int x1, const int y1,
                           const int x2, const int y2,
                           const CImg<tc>& texture,
                           const int tx0, const int ty0,
                           const int tx1, const int ty1,
                           const int tx2, const int ty2,
                           const float opacity=1,
                           const float brightness=1) {
      if (is_empty()) return *this;
      if (!texture || texture.dim<dim)
        throw CImgArgumentException("CImg<%s>::draw_triangle() : Specified texture (%u,%u,%u,%u,%p) is not a valid argument.",
                                    pixel_type(),texture.width,texture.height,texture.depth,texture.dim,texture.data);
      if (is_overlapped(texture)) return draw_triangle(x0,y0,x1,y1,x2,y2,+texture,tx0,ty0,tx1,ty1,tx2,ty2,opacity,brightness);
      static const T maxval = (T)cimg::min(cimg::type<T>::max(),cimg::type<tc>::max());
      const float
        nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0),
        nbrightness = brightness<0?0:(brightness>2?2:brightness);
      const int whz = width*height*depth, twhz = texture.width*texture.height*texture.depth, offx = dim*whz-1;
      int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2,
        ntx0 = tx0, nty0 = ty0, ntx1 = tx1, nty1 = ty1, ntx2 = tx2, nty2 = ty2;
      if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,ntx0,ntx1,nty0,nty1);
      if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,ntx0,ntx2,nty0,nty2);
      if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,ntx1,ntx2,nty1,nty2);
      if (ny0>=dimy() || ny2<0) return *this;
      _cimg_for_triangle3(*this,xleft0,txleft0,tyleft0,xright0,txright0,tyright0,y,
                          nx0,ny0,ntx0,nty0,nx1,ny1,ntx1,nty1,nx2,ny2,ntx2,nty2) {
        int
          xleft = xleft0, xright = xright0,
          txleft = txleft0, txright = txright0,
          tyleft = tyleft0, tyright = tyright0;
        if (xright<xleft) cimg::swap(xleft,xright,txleft,txright,tyleft,tyright);
        const int
          dx = xright - xleft,
          dtx = txright>txleft?txright - txleft:txleft - txright,
          dty = tyright>tyleft?tyright - tyleft:tyleft - tyright,
          rtx = dx?(txright - txleft)/dx:0,
          rty = dx?(tyright - tyleft)/dx:0,
          stx = txright>txleft?1:-1,
          sty = tyright>tyleft?1:-1,
          ndtx = dtx - (dx?dx*(dtx/dx):0),
          ndty = dty - (dx?dx*(dty/dx):0);
        int errtx = dx>>1, errty = errtx;
        if (xleft<0 && dx) {
          txleft-=xleft*(txright - txleft)/dx;
          tyleft-=xleft*(tyright - tyleft)/dx;
        }
        if (xleft<0) xleft = 0;
        if (xright>=dimx()-1) xright = dimx()-1;
        T* ptrd = ptr(xleft,y,0,0);
        if (opacity>=1) {
          if (nbrightness==1) for (int x = xleft; x<=xright; ++x) {
            const tc *col = texture.ptr(txleft,tyleft);
            cimg_forV(*this,k) {
              *ptrd = (T)*col;
              ptrd+=whz; col+=twhz;
            }
            ptrd-=offx;
            txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0);
            tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0);
          } else if (nbrightness<1) for (int x = xleft; x<=xright; ++x) {
            const tc *col = texture.ptr(txleft,tyleft);
            cimg_forV(*this,k) {
              *ptrd = (T)(nbrightness**col);
              ptrd+=whz; col+=twhz;
            }
            ptrd-=offx;
            txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0);
            tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0);
          } else for (int x = xleft; x<=xright; ++x) {
            const tc *col = texture.ptr(txleft,tyleft);
            cimg_forV(*this,k) {
              *ptrd = (T)((2-nbrightness)**(col++) + (nbrightness-1)*maxval);
              ptrd+=whz; col+=twhz;
            }
            ptrd-=offx;
            txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0);
            tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0);
          }
        } else {
          if (nbrightness==1) for (int x = xleft; x<=xright; ++x) {
            const tc *col = texture.ptr(txleft,tyleft);
            cimg_forV(*this,k) {
              *ptrd = (T)(nopacity**col + *ptrd*copacity);
              ptrd+=whz; col+=twhz;
            }
            ptrd-=offx;
            txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0);
            tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0);
          } else if (nbrightness<1) for (int x = xleft; x<=xright; ++x) {
            const tc *col = texture.ptr(txleft,tyleft);
            cimg_forV(*this,k) {
              *ptrd = (T)(nopacity*nbrightness**col + *ptrd*copacity);
              ptrd+=whz; col+=twhz;
            }
            ptrd-=offx;
            txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0);
            tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0);
          } else for (int x = xleft; x<=xright; ++x) {
            const tc *col = texture.ptr(txleft,tyleft);
            cimg_forV(*this,k) {
              const T val = (T)((2-nbrightness)**(col++) + (nbrightness-1)*maxval);
              *ptrd = (T)(nopacity*val + *ptrd*copacity);
              ptrd+=whz; col+=twhz;
            }
            ptrd-=offx;
            txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0);
            tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0);
          }
        }
      }
      return *this;
    }

    //! Draw a 2D textured triangle, with perspective correction.
    template<typename tc>
    CImg<T>& draw_triangle(const int x0, const int y0, const float z0,
                           const int x1, const int y1, const float z1,
                           const int x2, const int y2, const float z2,
                           const CImg<tc>& texture,
                           const int tx0, const int ty0,
                           const int tx1, const int ty1,
                           const int tx2, const int ty2,
                           const float opacity=1,
                           const float brightness=1) {
      if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this;
      if (!texture || texture.dim<dim)
        throw CImgArgumentException("CImg<%s>::draw_triangle() : Specified texture (%u,%u,%u,%u,%p) is not a valid argument.",
                                    pixel_type(),texture.width,texture.height,texture.depth,texture.dim,texture.data);
      if (is_overlapped(texture)) return draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,+texture,tx0,ty0,tx1,ty1,tx2,ty2,opacity,brightness);
      static const T maxval = (T)cimg::min(cimg::type<T>::max(),cimg::type<tc>::max());
      const float
        nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0),
        nbrightness = brightness<0?0:(brightness>2?2:brightness);
      const int whz = width*height*depth, twhz = texture.width*texture.height*texture.depth, offx = dim*whz-1;
      int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2;
      float
        ntx0 = tx0/z0, nty0 = ty0/z0,
        ntx1 = tx1/z1, nty1 = ty1/z1,
        ntx2 = tx2/z2, nty2 = ty2/z2,
        nz0 = 1/z0, nz1 = 1/z1, nz2 = 1/z2;
      if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,ntx0,ntx1,nty0,nty1,nz0,nz1);
      if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,ntx0,ntx2,nty0,nty2,nz0,nz2);
      if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,ntx1,ntx2,nty1,nty2,nz1,nz2);
      if (ny0>=dimy() || ny2<0) return *this;
      float
        ptxl = (ntx1 - ntx0)/(ny1 - ny0),
        ptxr = (ntx2 - ntx0)/(ny2 - ny0),
        ptxn = (ntx2 - ntx1)/(ny2 - ny1),
        ptyl = (nty1 - nty0)/(ny1 - ny0),
        ptyr = (nty2 - nty0)/(ny2 - ny0),
        ptyn = (nty2 - nty1)/(ny2 - ny1),
        pzl = (nz1 - nz0)/(ny1 - ny0),
        pzr = (nz2 - nz0)/(ny2 - ny0),
        pzn = (nz2 - nz1)/(ny2 - ny1),
        zr = ny0>=0?nz0:(nz0 - ny0*(nz2 - nz0)/(ny2 - ny0)),
        txr = ny0>=0?ntx0:(ntx0 - ny0*(ntx2 - ntx0)/(ny2 - ny0)),
        tyr = ny0>=0?nty0:(nty0 - ny0*(nty2 - nty0)/(ny2 - ny0)),
        zl = ny1>=0?(ny0>=0?nz0:(nz0 - ny0*(nz1 - nz0)/(ny1 - ny0))):(pzl=pzn,(nz1 - ny1*(nz2 - nz1)/(ny2 - ny1))),
        txl = ny1>=0?(ny0>=0?ntx0:(ntx0 - ny0*(ntx1 - ntx0)/(ny1 - ny0))):(ptxl=ptxn,(ntx1 - ny1*(ntx2 - ntx1)/(ny2 - ny1))),
        tyl = ny1>=0?(ny0>=0?nty0:(nty0 - ny0*(nty1 - nty0)/(ny1 - ny0))):(ptyl=ptyn,(nty1 - ny1*(nty2 - nty1)/(ny2 - ny1)));
      _cimg_for_triangle1(*this,xleft0,xright0,y,nx0,ny0,nx1,ny1,nx2,ny2) {
        if (y==ny1) { zl = nz1; txl = ntx1; tyl = nty1; pzl = pzn; ptxl = ptxn; ptyl = ptyn; }
        int xleft = xleft0, xright = xright0;
        float
          zleft = zl, zright = zr,
          txleft = txl, txright = txr,
          tyleft = tyl, tyright = tyr;
        if (xright<xleft) cimg::swap(xleft,xright,zleft,zright,txleft,txright,tyleft,tyright);
        const int dx = xright - xleft;
        const float
          pentez = (zright - zleft)/dx,
          pentetx = (txright - txleft)/dx,
          pentety = (tyright - tyleft)/dx;
        if (xleft<0 && dx) {
          zleft-=xleft*(zright - zleft)/dx;
          txleft-=xleft*(txright - txleft)/dx;
          tyleft-=xleft*(tyright - tyleft)/dx;
        }
        if (xleft<0) xleft = 0;
        if (xright>=dimx()-1) xright = dimx()-1;
        T* ptrd = ptr(xleft,y,0,0);
        if (opacity>=1) {
          if (nbrightness==1) for (int x = xleft; x<=xright; ++x) {
            const float invz = 1/zleft;
            const tc *col = texture.ptr((int)(txleft*invz),(int)(tyleft*invz));
            cimg_forV(*this,k) {
              *ptrd = (T)*col;
              ptrd+=whz; col+=twhz;
            }
            ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
          } else if (nbrightness<1) for (int x=xleft; x<=xright; ++x) {
            const float invz = 1/zleft;
            const tc *col = texture.ptr((int)(txleft*invz),(int)(tyleft*invz));
            cimg_forV(*this,k) {
              *ptrd = (T)(nbrightness**col);
              ptrd+=whz; col+=twhz;
            }
            ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
          } else for (int x = xleft; x<=xright; ++x) {
            const float invz = 1/zleft;
            const tc *col = texture.ptr((int)(txleft*invz),(int)(tyleft*invz));
            cimg_forV(*this,k) {
              *ptrd = (T)((2-nbrightness)**col + (nbrightness-1)*maxval);
              ptrd+=whz; col+=twhz;
            }
            ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
          }
        } else {
          if (nbrightness==1) for (int x = xleft; x<=xright; ++x) {
            const float invz = 1/zleft;
            const tc *col = texture.ptr((int)(txleft*invz),(int)(tyleft*invz));
            cimg_forV(*this,k) {
              *ptrd = (T)(nopacity**col + *ptrd*copacity);
              ptrd+=whz; col+=twhz;
            }
            ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
          } else if (nbrightness<1) for (int x = xleft; x<=xright; ++x) {
            const float invz = 1/zleft;
            const tc *col = texture.ptr((int)(txleft*invz),(int)(tyleft*invz));
            cimg_forV(*this,k) {
              *ptrd = (T)(nopacity*nbrightness**col + *ptrd*copacity);
              ptrd+=whz; col+=twhz;
            }
            ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
          } else for (int x = xleft; x<=xright; ++x) {
            const float invz = 1/zleft;
            const tc *col = texture.ptr((int)(txleft*invz),(int)(tyleft*invz));
            cimg_forV(*this,k) {
              const T val = (T)((2-nbrightness)**col + (nbrightness-1)*maxval);
              *ptrd = (T)(nopacity*val + *ptrd*copacity);
              ptrd+=whz; col+=twhz;
            }
            ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
          }
        }
        zr+=pzr; txr+=ptxr; tyr+=ptyr; zl+=pzl; txl+=ptxl; tyl+=ptyl;
      }
      return *this;
    }

    //! Draw a 2D textured triangle, with z-buffering and perspective correction.
    template<typename tc>
    CImg<T>& draw_triangle(float *const zbuffer,
                           const int x0, const int y0, const float z0,
                           const int x1, const int y1, const float z1,
                           const int x2, const int y2, const float z2,
                           const CImg<tc>& texture,
                           const int tx0, const int ty0,
                           const int tx1, const int ty1,
                           const int tx2, const int ty2,
                           const float opacity=1,
                           const float brightness=1) {
      if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this;
      if (!texture || texture.dim<dim)
        throw CImgArgumentException("CImg<%s>::draw_triangle() : Specified texture (%u,%u,%u,%u,%p) is not a valid argument.",
                                    pixel_type(),texture.width,texture.height,texture.depth,texture.dim,texture.data);
      if (is_overlapped(texture)) return draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,+texture,tx0,ty0,tx1,ty1,tx2,ty2,opacity,brightness);
      static const T maxval = (T)cimg::min(cimg::type<T>::max(),cimg::type<tc>::max());
      const float
        nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0),
        nbrightness = brightness<0?0:(brightness>2?2:brightness);
      const int whz = width*height*depth, twhz = texture.width*texture.height*texture.depth, offx = dim*whz;
      int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2;
      float
        ntx0 = tx0/z0, nty0 = ty0/z0,
        ntx1 = tx1/z1, nty1 = ty1/z1,
        ntx2 = tx2/z2, nty2 = ty2/z2,
        nz0 = 1/z0, nz1 = 1/z1, nz2 = 1/z2;
      if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,ntx0,ntx1,nty0,nty1,nz0,nz1);
      if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,ntx0,ntx2,nty0,nty2,nz0,nz2);
      if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,ntx1,ntx2,nty1,nty2,nz1,nz2);
      if (ny0>=dimy() || ny2<0) return *this;
      float
        ptxl = (ntx1 - ntx0)/(ny1 - ny0),
        ptxr = (ntx2 - ntx0)/(ny2 - ny0),
        ptxn = (ntx2 - ntx1)/(ny2 - ny1),
        ptyl = (nty1 - nty0)/(ny1 - ny0),
        ptyr = (nty2 - nty0)/(ny2 - ny0),
        ptyn = (nty2 - nty1)/(ny2 - ny1),
        pzl = (nz1 - nz0)/(ny1 - ny0),
        pzr = (nz2 - nz0)/(ny2 - ny0),
        pzn = (nz2 - nz1)/(ny2 - ny1),
        zr = ny0>=0?nz0:(nz0 - ny0*(nz2 - nz0)/(ny2 - ny0)),
        txr = ny0>=0?ntx0:(ntx0 - ny0*(ntx2 - ntx0)/(ny2 - ny0)),
        tyr = ny0>=0?nty0:(nty0 - ny0*(nty2 - nty0)/(ny2 - ny0)),
        zl = ny1>=0?(ny0>=0?nz0:(nz0 - ny0*(nz1 - nz0)/(ny1 - ny0))):(pzl=pzn,(nz1 - ny1*(nz2 - nz1)/(ny2 - ny1))),
        txl = ny1>=0?(ny0>=0?ntx0:(ntx0 - ny0*(ntx1 - ntx0)/(ny1 - ny0))):(ptxl=ptxn,(ntx1 - ny1*(ntx2 - ntx1)/(ny2 - ny1))),
        tyl = ny1>=0?(ny0>=0?nty0:(nty0 - ny0*(nty1 - nty0)/(ny1 - ny0))):(ptyl=ptyn,(nty1 - ny1*(nty2 - nty1)/(ny2 - ny1)));
      _cimg_for_triangle1(*this,xleft0,xright0,y,nx0,ny0,nx1,ny1,nx2,ny2) {
        if (y==ny1) { zl = nz1; txl = ntx1; tyl = nty1; pzl = pzn; ptxl = ptxn; ptyl = ptyn; }
        int xleft = xleft0, xright = xright0;
        float
          zleft = zl, zright = zr,
          txleft = txl, txright = txr,
          tyleft = tyl, tyright = tyr;
        if (xright<xleft) cimg::swap(xleft,xright,zleft,zright,txleft,txright,tyleft,tyright);
        const int dx = xright - xleft;
        const float
          pentez = (zright - zleft)/dx,
          pentetx = (txright - txleft)/dx,
          pentety = (tyright - tyleft)/dx;
        if (xleft<0 && dx) {
          zleft-=xleft*(zright - zleft)/dx;
          txleft-=xleft*(txright - txleft)/dx;
          tyleft-=xleft*(tyright - tyleft)/dx;
        }
        if (xleft<0) xleft = 0;
        if (xright>=dimx()-1) xright = dimx()-1;
        T *ptrd = ptr(xleft,y,0,0);
        float *ptrz = zbuffer + xleft + y*width;
        if (opacity>=1) {
          if (nbrightness==1) for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) {
            if (zleft>*ptrz) {
              *ptrz = zleft;
              const float invz = 1/zleft;
              const tc *col = texture.ptr((int)(txleft*invz),(int)(tyleft*invz));
              cimg_forV(*this,k) {
                *ptrd = (T)*col;
                ptrd+=whz; col+=twhz;
              }
              ptrd-=offx;
            }
            zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
          } else if (nbrightness<1) for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) {
            if (zleft>*ptrz) {
              *ptrz = zleft;
              const float invz = 1/zleft;
              const tc *col = texture.ptr((int)(txleft*invz),(int)(tyleft*invz));
              cimg_forV(*this,k) {
                *ptrd = (T)(nbrightness**col);
                ptrd+=whz; col+=twhz;
              }
              ptrd-=offx;
            }
            zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
          } else for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) {
            if (zleft>*ptrz) {
              *ptrz = zleft;
              const float invz = 1/zleft;
              const tc *col = texture.ptr((int)(txleft*invz),(int)(tyleft*invz));
              cimg_forV(*this,k) {
                *ptrd = (T)((2-nbrightness)**col + (nbrightness-1)*maxval);
                ptrd+=whz; col+=twhz;
              }
              ptrd-=offx;
            }
            zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
          }
        } else {
          if (nbrightness==1) for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) {
            if (zleft>*ptrz) {
              *ptrz = zleft;
              const float invz = 1/zleft;
              const tc *col = texture.ptr((int)(txleft*invz),(int)(tyleft*invz));
              cimg_forV(*this,k) {
                *ptrd = (T)(nopacity**col + *ptrd*copacity);
                ptrd+=whz; col+=twhz;
              }
              ptrd-=offx;
            }
            zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
          } else if (nbrightness<1) for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) {
            if (zleft>*ptrz) {
              *ptrz = zleft;
              const float invz = 1/zleft;
              const tc *col = texture.ptr((int)(txleft*invz),(int)(tyleft*invz));
              cimg_forV(*this,k) {
                *ptrd = (T)(nopacity*nbrightness**col + *ptrd*copacity);
                ptrd+=whz; col+=twhz;
              }
              ptrd-=offx;
            }
            zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
          } else for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) {
            if (zleft>*ptrz) {
              *ptrz = zleft;
              const float invz = 1/zleft;
              const tc *col = texture.ptr((int)(txleft*invz),(int)(tyleft*invz));
              cimg_forV(*this,k) {
                const T val = (T)((2-nbrightness)**col + (nbrightness-1)*maxval);
                *ptrd = (T)(nopacity*val + *ptrd*copacity);
                ptrd+=whz; col+=twhz;
              }
              ptrd-=offx;
            }
            zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
          }
        }
        zr+=pzr; txr+=ptxr; tyr+=ptyr; zl+=pzl; txl+=ptxl; tyl+=ptyl;
      }
      return *this;
    }

    //! Draw a 2D Pseudo-Phong-shaded triangle.
    /**
       \param x0 = X-coordinate of the first corner in the instance image.
       \param y0 = Y-coordinate of the first corner in the instance image.
       \param x1 = X-coordinate of the second corner in the instance image.
       \param y1 = Y-coordinate of the second corner in the instance image.
       \param x2 = X-coordinate of the third corner in the instance image.
       \param y2 = Y-coordinate of the third corner in the instance image.
       \param color = array of dimv() values of type \c T, defining the global drawing color.
       \param light = light image.
       \param lx0 = X-coordinate of the first corner in the light image.
       \param ly0 = Y-coordinate of the first corner in the light image.
       \param lx1 = X-coordinate of the second corner in the light image.
       \param ly1 = Y-coordinate of the second corner in the light image.
       \param lx2 = X-coordinate of the third corner in the light image.
       \param ly2 = Y-coordinate of the third corner in the light image.
       \param opacity = opacity of the drawing.
       \note Clipping is supported, but texture coordinates do not support clipping.
    **/
    template<typename tc, typename tl>
    CImg<T>& draw_triangle(const int x0, const int y0,
                           const int x1, const int y1,
                           const int x2, const int y2,
                           const tc *const color,
                           const CImg<tl>& light,
                           const int lx0, const int ly0,
                           const int lx1, const int ly1,
                           const int lx2, const int ly2,
                           const float opacity=1) {
      if (is_empty()) return *this;
      if (!color)
        throw CImgArgumentException("CImg<%s>::draw_triangle : Specified color is (null).",
                                    pixel_type());
      if (!light)
        throw CImgArgumentException("CImg<%s>::draw_triangle() : Specified light texture (%u,%u,%u,%u,%p) is empty.",
                                    pixel_type(),light.width,light.height,light.depth,light.dim,light.data);
      if (is_overlapped(light)) return draw_triangle(x0,y0,x1,y1,x2,y2,color,+light,lx0,ly0,lx1,ly1,lx2,ly2,opacity);
      static const T maxval = (T)cimg::min(cimg::type<T>::max(),cimg::type<tc>::max());
      const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
      int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2,
        nlx0 = lx0, nly0 = ly0, nlx1 = lx1, nly1 = ly1, nlx2 = lx2, nly2 = ly2;
      const int whz = width*height*depth, offx = dim*whz-1;
      if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,nlx0,nlx1,nly0,nly1);
      if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,nlx0,nlx2,nly0,nly2);
      if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,nlx1,nlx2,nly1,nly2);
      if (ny0>=dimy() || ny2<0) return *this;
      _cimg_for_triangle3(*this,xleft0,lxleft0,lyleft0,xright0,lxright0,lyright0,y,
                          nx0,ny0,nlx0,nly0,nx1,ny1,nlx1,nly1,nx2,ny2,nlx2,nly2) {
        int
          xleft = xleft0, xright = xright0,
          lxleft = lxleft0, lxright = lxright0,
          lyleft = lyleft0, lyright = lyright0;
        if (xright<xleft) cimg::swap(xleft,xright,lxleft,lxright,lyleft,lyright);
        const int
          dx = xright - xleft,
          dlx = lxright>lxleft?lxright - lxleft:lxleft - lxright,
          dly = lyright>lyleft?lyright - lyleft:lyleft - lyright,
          rlx = dx?(lxright - lxleft)/dx:0,
          rly = dx?(lyright - lyleft)/dx:0,
          slx = lxright>lxleft?1:-1,
          sly = lyright>lyleft?1:-1,
          ndlx = dlx - (dx?dx*(dlx/dx):0),
          ndly = dly - (dx?dx*(dly/dx):0);
        int errlx = dx>>1, errly = errlx;
        if (xleft<0 && dx) {
          lxleft-=xleft*(lxright - lxleft)/dx;
          lyleft-=xleft*(lyright - lyleft)/dx;
        }
        if (xleft<0) xleft = 0;
        if (xright>=dimx()-1) xright = dimx()-1;
        T* ptrd = ptr(xleft,y,0,0);
        if (opacity>=1) for (int x = xleft; x<=xright; ++x) {
          const tl l = light(lxleft,lyleft);
          const tc *col = color;
          cimg_forV(*this,k) {
            *ptrd = (T)(l<1?l**(col++):((2-l)**(col++)+(l-1)*maxval));
            ptrd+=whz;
          }
          ptrd-=offx;
          lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0);
          lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0);
        } else  for (int x = xleft; x<=xright; ++x) {
          const tl l = light(lxleft,lyleft);
          const tc *col = color;
          cimg_forV(*this,k) {
            const T val = (T)(l<1?l**(col++):((2-l)**(col++)+(l-1)*maxval));
            *ptrd = (T)(nopacity*val + *ptrd*copacity);
            ptrd+=whz;
          }
          ptrd-=offx;
          lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0);
          lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0);
        }
      }
      return *this;
    }

    //! Draw a 2D Pseudo-Phong-shaded triangle.
    template<typename tc, typename tl>
    CImg<T>& draw_triangle(const int x0, const int y0,
                           const int x1, const int y1,
                           const int x2, const int y2,
                           const CImg<tc>& color,
                           const CImg<tl>& light,
                           const int lx0, const int ly0,
                           const int lx1, const int ly1,
                           const int lx2, const int ly2,
                           const float opacity=1) {
      return draw_triangle(x0,y0,x1,y1,x2,y2,color.data,light,lx0,ly0,lx1,ly1,lx2,ly2,opacity);
    }

    //! Draw a 2D Pseudo-Phong-shaded triangle, with z-buffering.
    template<typename tc, typename tl>
    CImg<T>& draw_triangle(float *const zbuffer,
                           const int x0, const int y0, const float z0,
                           const int x1, const int y1, const float z1,
                           const int x2, const int y2, const float z2,
                           const tc *const color,
                           const CImg<tl>& light,
                           const int lx0, const int ly0,
                           const int lx1, const int ly1,
                           const int lx2, const int ly2,
                           const float opacity=1) {
      if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this;
      if (!color)
        throw CImgArgumentException("CImg<%s>::draw_triangle() : Specified color is (null).",
                                    pixel_type());
      if (!light)
        throw CImgArgumentException("CImg<%s>::draw_triangle() : Specified light texture (%u,%u,%u,%u,%p) is empty.",
                                    pixel_type(),light.width,light.height,light.depth,light.dim,light.data);
      if (is_overlapped(light)) return draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color,
                                                     +light,lx0,ly0,lx1,ly1,lx2,ly2,opacity);
      static const T maxval = (T)cimg::min(cimg::type<T>::max(),cimg::type<tc>::max());
      const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
      const int whz = width*height*depth, offx = dim*whz;
      int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2,
        nlx0 = lx0, nly0 = ly0, nlx1 = lx1, nly1 = ly1, nlx2 = lx2, nly2 = ly2;
      float nz0 = 1/z0, nz1 = 1/z1, nz2 = 1/z2;
      if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,nlx0,nlx1,nly0,nly1,nz0,nz1);
      if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,nlx0,nlx2,nly0,nly2,nz0,nz2);
      if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,nlx1,nlx2,nly1,nly2,nz1,nz2);
      if (ny0>=dimy() || ny2<0) return *this;
      float
        pzl = (nz1 - nz0)/(ny1 - ny0),
        pzr = (nz2 - nz0)/(ny2 - ny0),
        pzn = (nz2 - nz1)/(ny2 - ny1),
        zr = ny0>=0?nz0:(nz0 - ny0*(nz2 - nz0)/(ny2 - ny0)),
        zl = ny1>=0?(ny0>=0?nz0:(nz0 - ny0*(nz1 - nz0)/(ny1 - ny0))):(pzl=pzn,(nz1 - ny1*(nz2 - nz1)/(ny2 - ny1)));
      _cimg_for_triangle3(*this,xleft0,lxleft0,lyleft0,xright0,lxright0,lyright0,y,
                          nx0,ny0,nlx0,nly0,nx1,ny1,nlx1,nly1,nx2,ny2,nlx2,nly2) {
        if (y==ny1) { zl = nz1; pzl = pzn; }
        int
          xleft = xleft0, xright = xright0,
          lxleft = lxleft0, lxright = lxright0,
          lyleft = lyleft0, lyright = lyright0;
        float zleft = zl, zright = zr;
        if (xright<xleft) cimg::swap(xleft,xright,zleft,zright,lxleft,lxright,lyleft,lyright);
        const int
          dx = xright - xleft,
          dlx = lxright>lxleft?lxright - lxleft:lxleft - lxright,
          dly = lyright>lyleft?lyright - lyleft:lyleft - lyright,
          rlx = dx?(lxright - lxleft)/dx:0,
          rly = dx?(lyright - lyleft)/dx:0,
          slx = lxright>lxleft?1:-1,
          sly = lyright>lyleft?1:-1,
          ndlx = dlx - (dx?dx*(dlx/dx):0),
          ndly = dly - (dx?dx*(dly/dx):0);
        const float pentez = (zright - zleft)/dx;
        int errlx = dx>>1, errly = errlx;
        if (xleft<0 && dx) {
          zleft-=xleft*(zright - zleft)/dx;
          lxleft-=xleft*(lxright - lxleft)/dx;
          lyleft-=xleft*(lyright - lyleft)/dx;
        }
        if (xleft<0) xleft = 0;
        if (xright>=dimx()-1) xright = dimx()-1;
        T *ptrd = ptr(xleft,y,0,0);
        float *ptrz = zbuffer + xleft + y*width;
        if (opacity>=1) for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) {
          if (zleft>*ptrz) {
            *ptrz = zleft;
            const tl l = light(lxleft,lyleft);
            const tc *col = color;
            cimg_forV(*this,k) {
              const tc cval = *(col++);
              *ptrd = (T)(l<1?l*cval:(2-l)*cval+(l-1)*maxval);
              ptrd+=whz;
            }
            ptrd-=offx;
          }
          zleft+=pentez;
          lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0);
          lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0);
        } else for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) {
          if (zleft>*ptrz) {
            *ptrz = zleft;
            const tl l = light(lxleft,lyleft);
            const tc *col = color;
            cimg_forV(*this,k) {
              const tc cval = *(col++);
              const T val = (T)(l<1?l*cval:(2-l)*cval+(l-1)*maxval);
              *ptrd = (T)(nopacity*val + *ptrd*copacity);
              ptrd+=whz;
            }
            ptrd-=offx;
          }
          zleft+=pentez;
          lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0);
          lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0);
        }
        zr+=pzr; zl+=pzl;
      }
      return *this;
    }

    //! Draw a 2D Pseudo-Phong-shaded triangle, with z-buffering.
    template<typename tc, typename tl>
    CImg<T>& draw_triangle(float *const zbuffer,
                           const int x0, const int y0, const float z0,
                           const int x1, const int y1, const float z1,
                           const int x2, const int y2, const float z2,
                           const CImg<tc>& color,
                           const CImg<tl>& light,
                           const int lx0, const int ly0,
                           const int lx1, const int ly1,
                           const int lx2, const int ly2,
                           const float opacity=1) {
      return draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color.data,light,lx0,ly0,lx1,ly1,lx2,ly2,opacity);
    }

    //! Draw a 2D Gouraud-shaded textured triangle.
    /**
       \param x0 = X-coordinate of the first corner in the instance image.
       \param y0 = Y-coordinate of the first corner in the instance image.
       \param x1 = X-coordinate of the second corner in the instance image.
       \param y1 = Y-coordinate of the second corner in the instance image.
       \param x2 = X-coordinate of the third corner in the instance image.
       \param y2 = Y-coordinate of the third corner in the instance image.
       \param texture = texture image used to fill the triangle.
       \param tx0 = X-coordinate of the first corner in the texture image.
       \param ty0 = Y-coordinate of the first corner in the texture image.
       \param tx1 = X-coordinate of the second corner in the texture image.
       \param ty1 = Y-coordinate of the second corner in the texture image.
       \param tx2 = X-coordinate of the third corner in the texture image.
       \param ty2 = Y-coordinate of the third corner in the texture image.
       \param brightness0 = brightness value of the first corner.
       \param brightness1 = brightness value of the second corner.
       \param brightness2 = brightness value of the third corner.
       \param opacity = opacity of the drawing.
       \note Clipping is supported, but texture coordinates do not support clipping.
    **/
    template<typename tc>
    CImg<T>& draw_triangle(const int x0, const int y0,
                           const int x1, const int y1,
                           const int x2, const int y2,
                           const CImg<tc>& texture,
                           const int tx0, const int ty0,
                           const int tx1, const int ty1,
                           const int tx2, const int ty2,
                           const float brightness0,
                           const float brightness1,
                           const float brightness2,
                           const float opacity=1) {
      if (is_empty()) return *this;
      if (!texture || texture.dim<dim)
        throw CImgArgumentException("CImg<%s>::draw_triangle() : Specified texture (%u,%u,%u,%u,%p) is not a valid argument.",
                                    pixel_type(),texture.width,texture.height,texture.depth,texture.dim,texture.data);
      if (is_overlapped(texture))
        return draw_triangle(x0,y0,x1,y1,x2,y2,+texture,tx0,ty0,tx1,ty1,tx2,ty2,brightness0,brightness1,brightness2,opacity);
      static const T maxval = (T)cimg::min(cimg::type<T>::max(),cimg::type<tc>::max());
      const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
      const int whz = width*height*depth, twhz = texture.width*texture.height*texture.depth, offx = dim*whz-1;
      int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2,
        ntx0 = tx0, nty0 = ty0, ntx1 = tx1, nty1 = ty1, ntx2 = tx2, nty2 = ty2,
        nc0 = (int)((brightness0<0?0:(brightness0>2?2:brightness0))*256),
        nc1 = (int)((brightness1<0?0:(brightness1>2?2:brightness1))*256),
        nc2 = (int)((brightness2<0?0:(brightness2>2?2:brightness2))*256);
      if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,ntx0,ntx1,nty0,nty1,nc0,nc1);
      if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,ntx0,ntx2,nty0,nty2,nc0,nc2);
      if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,ntx1,ntx2,nty1,nty2,nc1,nc2);
      if (ny0>=dimy() || ny2<0) return *this;
      _cimg_for_triangle4(*this,xleft0,cleft0,txleft0,tyleft0,xright0,cright0,txright0,tyright0,y,
                          nx0,ny0,nc0,ntx0,nty0,nx1,ny1,nc1,ntx1,nty1,nx2,ny2,nc2,ntx2,nty2) {
        int
          xleft = xleft0, xright = xright0,
          cleft = cleft0, cright = cright0,
          txleft = txleft0, txright = txright0,
          tyleft = tyleft0, tyright = tyright0;
        if (xright<xleft) cimg::swap(xleft,xright,cleft,cright,txleft,txright,tyleft,tyright);
        const int
          dx = xright - xleft,
          dc = cright>cleft?cright - cleft:cleft - cright,
          dtx = txright>txleft?txright - txleft:txleft - txright,
          dty = tyright>tyleft?tyright - tyleft:tyleft - tyright,
          rc = dx?(cright - cleft)/dx:0,
          rtx = dx?(txright - txleft)/dx:0,
          rty = dx?(tyright - tyleft)/dx:0,
          sc = cright>cleft?1:-1,
          stx = txright>txleft?1:-1,
          sty = tyright>tyleft?1:-1,
          ndc = dc - (dx?dx*(dc/dx):0),
          ndtx = dtx - (dx?dx*(dtx/dx):0),
          ndty = dty - (dx?dx*(dty/dx):0);
        int errc = dx>>1, errtx = errc, errty = errc;
        if (xleft<0 && dx) {
          cleft-=xleft*(cright - cleft)/dx;
          txleft-=xleft*(txright - txleft)/dx;
          tyleft-=xleft*(tyright - tyleft)/dx;
        }
        if (xleft<0) xleft = 0;
        if (xright>=dimx()-1) xright = dimx()-1;
        T* ptrd = ptr(xleft,y,0,0);
        if (opacity>=1) for (int x = xleft; x<=xright; ++x) {
          const tc *col = texture.ptr(txleft,tyleft);
          cimg_forV(*this,k) {
            *ptrd = (T)(cleft<256?cleft**col/256:((512-cleft)**col+(cleft-256)*maxval)/256);
            ptrd+=whz; col+=twhz;
          }
          ptrd-=offx;
          cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0);
          txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0);
          tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0);
        } else for (int x = xleft; x<=xright; ++x) {
          const tc *col = texture.ptr(txleft,tyleft);
          cimg_forV(*this,k) {
            const T val = (T)(cleft<256?cleft**col/256:((512-cleft)**col+(cleft-256)*maxval)/256);
            *ptrd = (T)(nopacity*val + *ptrd*copacity);
            ptrd+=whz; col+=twhz;
          }
          ptrd-=offx;
          cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0);
          txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0);
          tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0);
        }
      }
      return *this;
    }

    //! Draw a 2D Gouraud-shaded textured triangle, with perspective correction.
    template<typename tc>
    CImg<T>& draw_triangle(const int x0, const int y0, const float z0,
                           const int x1, const int y1, const float z1,
                           const int x2, const int y2, const float z2,
                           const CImg<tc>& texture,
                           const int tx0, const int ty0,
                           const int tx1, const int ty1,
                           const int tx2, const int ty2,
                           const float brightness0,
                           const float brightness1,
                           const float brightness2,
                           const float opacity=1) {
      if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this;
      if (!texture || texture.dim<dim)
        throw CImgArgumentException("CImg<%s>::draw_triangle() : Specified texture (%u,%u,%u,%u,%p) is not a valid argument.",
                                    pixel_type(),texture.width,texture.height,texture.depth,texture.dim,texture.data);
      if (is_overlapped(texture)) return draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,+texture,tx0,ty0,tx1,ty1,tx2,ty2,
                                                       brightness0,brightness1,brightness2,opacity);
      static const T maxval = (T)cimg::min(cimg::type<T>::max(),cimg::type<tc>::max());
      const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
      const int whz = width*height*depth, twhz = texture.width*texture.height*texture.depth, offx = dim*whz-1;
      int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2,
        nc0 = (int)((brightness0<0?0:(brightness0>2?2:brightness0))*256),
        nc1 = (int)((brightness1<0?0:(brightness1>2?2:brightness1))*256),
        nc2 = (int)((brightness2<0?0:(brightness2>2?2:brightness2))*256);
      float
        ntx0 = tx0/z0, nty0 = ty0/z0,
        ntx1 = tx1/z1, nty1 = ty1/z1,
        ntx2 = tx2/z2, nty2 = ty2/z2,
        nz0 = 1/z0, nz1 = 1/z1, nz2 = 1/z2;
      if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,ntx0,ntx1,nty0,nty1,nz0,nz1,nc0,nc1);
      if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,ntx0,ntx2,nty0,nty2,nz0,nz2,nc0,nc2);
      if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,ntx1,ntx2,nty1,nty2,nz1,nz2,nc1,nc2);
      if (ny0>=dimy() || ny2<0) return *this;
      float
        ptxl = (ntx1 - ntx0)/(ny1 - ny0),
        ptxr = (ntx2 - ntx0)/(ny2 - ny0),
        ptxn = (ntx2 - ntx1)/(ny2 - ny1),
        ptyl = (nty1 - nty0)/(ny1 - ny0),
        ptyr = (nty2 - nty0)/(ny2 - ny0),
        ptyn = (nty2 - nty1)/(ny2 - ny1),
        pzl = (nz1 - nz0)/(ny1 - ny0),
        pzr = (nz2 - nz0)/(ny2 - ny0),
        pzn = (nz2 - nz1)/(ny2 - ny1),
        zr = ny0>=0?nz0:(nz0 - ny0*(nz2 - nz0)/(ny2 - ny0)),
        txr = ny0>=0?ntx0:(ntx0 - ny0*(ntx2 - ntx0)/(ny2 - ny0)),
        tyr = ny0>=0?nty0:(nty0 - ny0*(nty2 - nty0)/(ny2 - ny0)),
        zl = ny1>=0?(ny0>=0?nz0:(nz0 - ny0*(nz1 - nz0)/(ny1 - ny0))):(pzl=pzn,(nz1 - ny1*(nz2 - nz1)/(ny2 - ny1))),
        txl = ny1>=0?(ny0>=0?ntx0:(ntx0 - ny0*(ntx1 - ntx0)/(ny1 - ny0))):(ptxl=ptxn,(ntx1 - ny1*(ntx2 - ntx1)/(ny2 - ny1))),
        tyl = ny1>=0?(ny0>=0?nty0:(nty0 - ny0*(nty1 - nty0)/(ny1 - ny0))):(ptyl=ptyn,(nty1 - ny1*(nty2 - nty1)/(ny2 - ny1)));
      _cimg_for_triangle2(*this,xleft0,cleft0,xright0,cright0,y,nx0,ny0,nc0,nx1,ny1,nc1,nx2,ny2,nc2) {
        if (y==ny1) { zl = nz1; txl = ntx1; tyl = nty1; pzl = pzn; ptxl = ptxn; ptyl = ptyn; }
        int
          xleft = xleft0, xright = xright0,
          cleft = cleft0, cright = cright0;
        float
          zleft = zl, zright = zr,
          txleft = txl, txright = txr,
          tyleft = tyl, tyright = tyr;
        if (xright<xleft) cimg::swap(xleft,xright,zleft,zright,txleft,txright,tyleft,tyright,cleft,cright);
        const int
          dx = xright - xleft,
          dc = cright>cleft?cright - cleft:cleft - cright,
          rc = dx?(cright - cleft)/dx:0,
          sc = cright>cleft?1:-1,
          ndc = dc - (dx?dx*(dc/dx):0);
        const float
          pentez = (zright - zleft)/dx,
          pentetx = (txright - txleft)/dx,
          pentety = (tyright - tyleft)/dx;
        int errc = dx>>1;
        if (xleft<0 && dx) {
          cleft-=xleft*(cright - cleft)/dx;
          zleft-=xleft*(zright - zleft)/dx;
          txleft-=xleft*(txright - txleft)/dx;
          tyleft-=xleft*(tyright - tyleft)/dx;
        }
        if (xleft<0) xleft = 0;
        if (xright>=dimx()-1) xright = dimx()-1;
        T* ptrd = ptr(xleft,y,0,0);
        if (opacity>=1) for (int x = xleft; x<=xright; ++x) {
          const float invz = 1/zleft;
          const tc *col = texture.ptr((int)(txleft*invz),(int)(tyleft*invz));
          cimg_forV(*this,k) {
            *ptrd = (T)(cleft<256?cleft**col/256:((512-cleft)**col+(cleft-256)*maxval)/256);
            ptrd+=whz; col+=twhz;
          }
          ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
          cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0);
        } else for (int x = xleft; x<=xright; ++x) {
          const float invz = 1/zleft;
          const tc *col = texture.ptr((int)(txleft*invz),(int)(tyleft*invz));
          cimg_forV(*this,k) {
            const T val = (T)(cleft<256?cleft**col/256:((512-cleft)**col+(cleft-256)*maxval)/256);
            *ptrd = (T)(nopacity*val + *ptrd*copacity);
            ptrd+=whz; col+=twhz;
          }
          ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
          cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0);
        }
        zr+=pzr; txr+=ptxr; tyr+=ptyr; zl+=pzl; txl+=ptxl; tyl+=ptyl;
      }
      return *this;
    }

    //! Draw a 2D Gouraud-shaded textured triangle, with z-buffering and perspective correction.
    template<typename tc>
    CImg<T>& draw_triangle(float *const zbuffer,
                           const int x0, const int y0, const float z0,
                           const int x1, const int y1, const float z1,
                           const int x2, const int y2, const float z2,
                           const CImg<tc>& texture,
                           const int tx0, const int ty0,
                           const int tx1, const int ty1,
                           const int tx2, const int ty2,
                           const float brightness0,
                           const float brightness1,
                           const float brightness2,
                           const float opacity=1) {
      if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this;
      if (!texture || texture.dim<dim)
        throw CImgArgumentException("CImg<%s>::draw_triangle() : Specified texture (%u,%u,%u,%u,%p) is not a valid argument.",
                                    pixel_type(),texture.width,texture.height,texture.depth,texture.dim,texture.data);
      if (is_overlapped(texture)) return draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,+texture,tx0,ty0,tx1,ty1,tx2,ty2,
                                                       brightness0,brightness1,brightness2,opacity);
      static const T maxval = (T)cimg::min(cimg::type<T>::max(),cimg::type<tc>::max());
      const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
      const int whz = width*height*depth, twhz = texture.width*texture.height*texture.depth, offx = dim*whz;
      int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2,
        nc0 = (int)((brightness0<0?0:(brightness0>2?2:brightness0))*256),
        nc1 = (int)((brightness1<0?0:(brightness1>2?2:brightness1))*256),
        nc2 = (int)((brightness2<0?0:(brightness2>2?2:brightness2))*256);
      float
        ntx0 = tx0/z0, nty0 = ty0/z0,
        ntx1 = tx1/z1, nty1 = ty1/z1,
        ntx2 = tx2/z2, nty2 = ty2/z2,
        nz0 = 1/z0, nz1 = 1/z1, nz2 = 1/z2;
      if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,ntx0,ntx1,nty0,nty1,nz0,nz1,nc0,nc1);
      if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,ntx0,ntx2,nty0,nty2,nz0,nz2,nc0,nc2);
      if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,ntx1,ntx2,nty1,nty2,nz1,nz2,nc1,nc2);
      if (ny0>=dimy() || ny2<0) return *this;
      float
        ptxl = (ntx1 - ntx0)/(ny1 - ny0),
        ptxr = (ntx2 - ntx0)/(ny2 - ny0),
        ptxn = (ntx2 - ntx1)/(ny2 - ny1),
        ptyl = (nty1 - nty0)/(ny1 - ny0),
        ptyr = (nty2 - nty0)/(ny2 - ny0),
        ptyn = (nty2 - nty1)/(ny2 - ny1),
        pzl = (nz1 - nz0)/(ny1 - ny0),
        pzr = (nz2 - nz0)/(ny2 - ny0),
        pzn = (nz2 - nz1)/(ny2 - ny1),
        zr = ny0>=0?nz0:(nz0 - ny0*(nz2 - nz0)/(ny2 - ny0)),
        txr = ny0>=0?ntx0:(ntx0 - ny0*(ntx2 - ntx0)/(ny2 - ny0)),
        tyr = ny0>=0?nty0:(nty0 - ny0*(nty2 - nty0)/(ny2 - ny0)),
        zl = ny1>=0?(ny0>=0?nz0:(nz0 - ny0*(nz1 - nz0)/(ny1 - ny0))):(pzl=pzn,(nz1 - ny1*(nz2 - nz1)/(ny2 - ny1))),
        txl = ny1>=0?(ny0>=0?ntx0:(ntx0 - ny0*(ntx1 - ntx0)/(ny1 - ny0))):(ptxl=ptxn,(ntx1 - ny1*(ntx2 - ntx1)/(ny2 - ny1))),
        tyl = ny1>=0?(ny0>=0?nty0:(nty0 - ny0*(nty1 - nty0)/(ny1 - ny0))):(ptyl=ptyn,(nty1 - ny1*(nty2 - nty1)/(ny2 - ny1)));
      _cimg_for_triangle2(*this,xleft0,cleft0,xright0,cright0,y,nx0,ny0,nc0,nx1,ny1,nc1,nx2,ny2,nc2) {
        if (y==ny1) { zl = nz1; txl = ntx1; tyl = nty1; pzl = pzn; ptxl = ptxn; ptyl = ptyn; }
        int
          xleft = xleft0, xright = xright0,
          cleft = cleft0, cright = cright0;
        float
          zleft = zl, zright = zr,
          txleft = txl, txright = txr,
          tyleft = tyl, tyright = tyr;
        if (xright<xleft) cimg::swap(xleft,xright,zleft,zright,txleft,txright,tyleft,tyright,cleft,cright);
        const int
          dx = xright - xleft,
          dc = cright>cleft?cright - cleft:cleft - cright,
          rc = dx?(cright - cleft)/dx:0,
          sc = cright>cleft?1:-1,
          ndc = dc - (dx?dx*(dc/dx):0);
        const float
          pentez = (zright - zleft)/dx,
          pentetx = (txright - txleft)/dx,
          pentety = (tyright - tyleft)/dx;
        int errc = dx>>1;
        if (xleft<0 && dx) {
          cleft-=xleft*(cright - cleft)/dx;
          zleft-=xleft*(zright - zleft)/dx;
          txleft-=xleft*(txright - txleft)/dx;
          tyleft-=xleft*(tyright - tyleft)/dx;
        }
        if (xleft<0) xleft = 0;
        if (xright>=dimx()-1) xright = dimx()-1;
        T* ptrd = ptr(xleft,y);
        float *ptrz = zbuffer + xleft + y*width;
        if (opacity>=1) for (int x = xleft; x<=xright; ++x, ++ptrd, ++ptrz) {
          if (zleft>*ptrz) {
            *ptrz = zleft;
            const float invz = 1/zleft;
            const tc *col = texture.ptr((int)(txleft*invz),(int)(tyleft*invz));
            cimg_forV(*this,k) {
              *ptrd = (T)(cleft<256?cleft**col/256:((512-cleft)**col+(cleft-256)*maxval)/256);
              ptrd+=whz; col+=twhz;
            }
            ptrd-=offx;
          }
          zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
          cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0);
        } else for (int x = xleft; x<=xright; ++x, ++ptrd, ++ptrz) {
          if (zleft>*ptrz) {
            *ptrz = zleft;
            const float invz = 1/zleft;
            const tc *col = texture.ptr((int)(txleft*invz),(int)(tyleft*invz));
            cimg_forV(*this,k) {
              const T val = (T)(cleft<256?cleft**col/256:((512-cleft)**col+(cleft-256)*maxval)/256);
              *ptrd = (T)(nopacity*val + *ptrd*copacity);
              ptrd+=whz; col+=twhz;
            }
            ptrd-=offx;
          }
          zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
          cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0);
        }
        zr+=pzr; txr+=ptxr; tyr+=ptyr; zl+=pzl; txl+=ptxl; tyl+=ptyl;
      }
      return *this;
    }

    //! Draw a 2D Pseudo-Phong-shaded textured triangle.
    /**
       \param x0 = X-coordinate of the first corner in the instance image.
       \param y0 = Y-coordinate of the first corner in the instance image.
       \param x1 = X-coordinate of the second corner in the instance image.
       \param y1 = Y-coordinate of the second corner in the instance image.
       \param x2 = X-coordinate of the third corner in the instance image.
       \param y2 = Y-coordinate of the third corner in the instance image.
       \param texture = texture image used to fill the triangle.
       \param tx0 = X-coordinate of the first corner in the texture image.
       \param ty0 = Y-coordinate of the first corner in the texture image.
       \param tx1 = X-coordinate of the second corner in the texture image.
       \param ty1 = Y-coordinate of the second corner in the texture image.
       \param tx2 = X-coordinate of the third corner in the texture image.
       \param ty2 = Y-coordinate of the third corner in the texture image.
       \param light = light image.
       \param lx0 = X-coordinate of the first corner in the light image.
       \param ly0 = Y-coordinate of the first corner in the light image.
       \param lx1 = X-coordinate of the second corner in the light image.
       \param ly1 = Y-coordinate of the second corner in the light image.
       \param lx2 = X-coordinate of the third corner in the light image.
       \param ly2 = Y-coordinate of the third corner in the light image.
       \param opacity = opacity of the drawing.
       \note Clipping is supported, but texture coordinates do not support clipping.
    **/
    template<typename tc, typename tl>
    CImg<T>& draw_triangle(const int x0, const int y0,
                           const int x1, const int y1,
                           const int x2, const int y2,
                           const CImg<tc>& texture,
                           const int tx0, const int ty0,
                           const int tx1, const int ty1,
                           const int tx2, const int ty2,
                           const CImg<tl>& light,
                           const int lx0, const int ly0,
                           const int lx1, const int ly1,
                           const int lx2, const int ly2,
                           const float opacity=1) {
      if (is_empty()) return *this;
      if (!texture || texture.dim<dim)
        throw CImgArgumentException("CImg<%s>::draw_triangle() : Specified texture (%u,%u,%u,%u,%p) is not a valid argument.",
                                    pixel_type(),texture.width,texture.height,texture.depth,texture.dim,texture.data);
      if (!light)
        throw CImgArgumentException("CImg<%s>::draw_triangle() : Specified light texture (%u,%u,%u,%u,%p) is empty.",
                                    pixel_type(),light.width,light.height,light.depth,light.dim,light.data);
      if (is_overlapped(texture)) return draw_triangle(x0,y0,x1,y1,x2,y2,+texture,tx0,ty0,tx1,ty1,tx2,ty2,light,lx0,ly0,lx1,ly1,lx2,ly2,opacity);
      if (is_overlapped(light))   return draw_triangle(x0,y0,x1,y1,x2,y2,texture,tx0,ty0,tx1,ty1,tx2,ty2,+light,lx0,ly0,lx1,ly1,lx2,ly2,opacity);
      static const T maxval = (T)cimg::min(cimg::type<T>::max(),cimg::type<tc>::max());
      const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
      const int whz = width*height*depth, twhz = texture.width*texture.height*texture.depth, offx = dim*whz-1;
      int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2,
        ntx0 = tx0, nty0 = ty0, ntx1 = tx1, nty1 = ty1, ntx2 = tx2, nty2 = ty2,
        nlx0 = lx0, nly0 = ly0, nlx1 = lx1, nly1 = ly1, nlx2 = lx2, nly2 = ly2;
      if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,ntx0,ntx1,nty0,nty1,nlx0,nlx1,nly0,nly1);
      if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,ntx0,ntx2,nty0,nty2,nlx0,nlx2,nly0,nly2);
      if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,ntx1,ntx2,nty1,nty2,nlx1,nlx2,nly1,nly2);
      if (ny0>=dimy() || ny2<0) return *this;
      _cimg_for_triangle5(*this,xleft0,lxleft0,lyleft0,txleft0,tyleft0,xright0,lxright0,lyright0,txright0,tyright0,y,
                          nx0,ny0,nlx0,nly0,ntx0,nty0,nx1,ny1,nlx1,nly1,ntx1,nty1,nx2,ny2,nlx2,nly2,ntx2,nty2) {
        int
          xleft = xleft0, xright = xright0,
          lxleft = lxleft0, lxright = lxright0,
          lyleft = lyleft0, lyright = lyright0,
          txleft = txleft0, txright = txright0,
          tyleft = tyleft0, tyright = tyright0;
        if (xright<xleft) cimg::swap(xleft,xright,lxleft,lxright,lyleft,lyright,txleft,txright,tyleft,tyright);
        const int
          dx = xright - xleft,
          dlx = lxright>lxleft?lxright - lxleft:lxleft - lxright,
          dly = lyright>lyleft?lyright - lyleft:lyleft - lyright,
          dtx = txright>txleft?txright - txleft:txleft - txright,
          dty = tyright>tyleft?tyright - tyleft:tyleft - tyright,
          rlx = dx?(lxright - lxleft)/dx:0,
          rly = dx?(lyright - lyleft)/dx:0,
          rtx = dx?(txright - txleft)/dx:0,
          rty = dx?(tyright - tyleft)/dx:0,
          slx = lxright>lxleft?1:-1,
          sly = lyright>lyleft?1:-1,
          stx = txright>txleft?1:-1,
          sty = tyright>tyleft?1:-1,
          ndlx = dlx - (dx?dx*(dlx/dx):0),
          ndly = dly - (dx?dx*(dly/dx):0),
          ndtx = dtx - (dx?dx*(dtx/dx):0),
          ndty = dty - (dx?dx*(dty/dx):0);
        int errlx = dx>>1, errly = errlx, errtx = errlx, errty = errlx;
        if (xleft<0 && dx) {
          lxleft-=xleft*(lxright - lxleft)/dx;
          lyleft-=xleft*(lyright - lyleft)/dx;
          txleft-=xleft*(txright - txleft)/dx;
          tyleft-=xleft*(tyright - tyleft)/dx;
        }
        if (xleft<0) xleft = 0;
        if (xright>=dimx()-1) xright = dimx()-1;
        T* ptrd = ptr(xleft,y,0,0);
        if (opacity>=1) for (int x = xleft; x<=xright; ++x) {
          const tl l = light(lxleft,lyleft);
          const tc *col = texture.ptr(txleft,tyleft);
          cimg_forV(*this,k) {
            *ptrd = (T)(l<1?l**col:(2-l)**col+(l-1)*maxval);
            ptrd+=whz; col+=twhz;
          }
          ptrd-=offx;
          lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0);
          lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0);
          txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0);
          tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0);
        } else for (int x = xleft; x<=xright; ++x) {
          const tl l = light(lxleft,lyleft);
          const tc *col = texture.ptr(txleft,tyleft);
          cimg_forV(*this,k) {
            const T val = (T)(l<1?l**col:(2-l)**col+(l-1)*maxval);
            *ptrd = (T)(nopacity*val + *ptrd*copacity);
            ptrd+=whz; col+=twhz;
          }
          ptrd-=offx;
          lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0);
          lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0);
          txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0);
          tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0);
        }
      }
      return *this;
    }

    //! Draw a 2D Pseudo-Phong-shaded textured triangle, with perspective correction.
    template<typename tc, typename tl>
    CImg<T>& draw_triangle(const int x0, const int y0, const float z0,
                           const int x1, const int y1, const float z1,
                           const int x2, const int y2, const float z2,
                           const CImg<tc>& texture,
                           const int tx0, const int ty0,
                           const int tx1, const int ty1,
                           const int tx2, const int ty2,
                           const CImg<tl>& light,
                           const int lx0, const int ly0,
                           const int lx1, const int ly1,
                           const int lx2, const int ly2,
                           const float opacity=1) {
      if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this;
      if (!texture || texture.dim<dim)
        throw CImgArgumentException("CImg<%s>::draw_triangle() : Specified texture (%u,%u,%u,%u,%p) is not a valid argument.",
                                    pixel_type(),texture.width,texture.height,texture.depth,texture.dim,texture.data);
      if (!light)
        throw CImgArgumentException("CImg<%s>::draw_triangle() : Specified light texture (%u,%u,%u,%u,%p) is empty.",
                                    pixel_type(),light.width,light.height,light.depth,light.dim,light.data);
      if (is_overlapped(texture)) return draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,+texture,tx0,ty0,tx1,ty1,tx2,ty2,light,lx0,ly0,lx1,ly1,lx2,ly2,opacity);
      if (is_overlapped(light)) return draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,texture,tx0,ty0,tx1,ty1,tx2,ty2,+light,lx0,ly0,lx1,ly1,lx2,ly2,opacity);
      static const T maxval = (T)cimg::min(cimg::type<T>::max(),cimg::type<tc>::max());
      const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
      const int whz = width*height*depth, twhz = texture.width*texture.height*texture.depth, offx = dim*whz-1;
      int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2,
        nlx0 = lx0, nly0 = ly0, nlx1 = lx1, nly1 = ly1, nlx2 = lx2, nly2 = ly2;
      float
        ntx0 = tx0/z0, nty0 = ty0/z0,
        ntx1 = tx1/z1, nty1 = ty1/z1,
        ntx2 = tx2/z2, nty2 = ty2/z2,
        nz0 = 1/z0, nz1 = 1/z1, nz2 = 1/z2;
      if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,ntx0,ntx1,nty0,nty1,nlx0,nlx1,nly0,nly1,nz0,nz1);
      if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,ntx0,ntx2,nty0,nty2,nlx0,nlx2,nly0,nly2,nz0,nz2);
      if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,ntx1,ntx2,nty1,nty2,nlx1,nlx2,nly1,nly2,nz1,nz2);
      if (ny0>=dimy() || ny2<0) return *this;
      float
        ptxl = (ntx1 - ntx0)/(ny1 - ny0),
        ptxr = (ntx2 - ntx0)/(ny2 - ny0),
        ptxn = (ntx2 - ntx1)/(ny2 - ny1),
        ptyl = (nty1 - nty0)/(ny1 - ny0),
        ptyr = (nty2 - nty0)/(ny2 - ny0),
        ptyn = (nty2 - nty1)/(ny2 - ny1),
        pzl = (nz1 - nz0)/(ny1 - ny0),
        pzr = (nz2 - nz0)/(ny2 - ny0),
        pzn = (nz2 - nz1)/(ny2 - ny1),
        zr = ny0>=0?nz0:(nz0 - ny0*(nz2 - nz0)/(ny2 - ny0)),
        txr = ny0>=0?ntx0:(ntx0 - ny0*(ntx2 - ntx0)/(ny2 - ny0)),
        tyr = ny0>=0?nty0:(nty0 - ny0*(nty2 - nty0)/(ny2 - ny0)),
        zl = ny1>=0?(ny0>=0?nz0:(nz0 - ny0*(nz1 - nz0)/(ny1 - ny0))):(pzl=pzn,(nz1 - ny1*(nz2 - nz1)/(ny2 - ny1))),
        txl = ny1>=0?(ny0>=0?ntx0:(ntx0 - ny0*(ntx1 - ntx0)/(ny1 - ny0))):(ptxl=ptxn,(ntx1 - ny1*(ntx2 - ntx1)/(ny2 - ny1))),
        tyl = ny1>=0?(ny0>=0?nty0:(nty0 - ny0*(nty1 - nty0)/(ny1 - ny0))):(ptyl=ptyn,(nty1 - ny1*(nty2 - nty1)/(ny2 - ny1)));
      _cimg_for_triangle3(*this,xleft0,lxleft0,lyleft0,xright0,lxright0,lyright0,y,
                          nx0,ny0,nlx0,nly0,nx1,ny1,nlx1,nly1,nx2,ny2,nlx2,nly2) {
        if (y==ny1) { zl = nz1; txl = ntx1; tyl = nty1; pzl = pzn; ptxl = ptxn; ptyl = ptyn; }
        int
          xleft = xleft0, xright = xright0,
          lxleft = lxleft0, lxright = lxright0,
          lyleft = lyleft0, lyright = lyright0;
        float
          zleft = zl, zright = zr,
          txleft = txl, txright = txr,
          tyleft = tyl, tyright = tyr;
        if (xright<xleft) cimg::swap(xleft,xright,zleft,zright,txleft,txright,tyleft,tyright,lxleft,lxright,lyleft,lyright);
        const int
          dx = xright - xleft,
          dlx = lxright>lxleft?lxright - lxleft:lxleft - lxright,
          dly = lyright>lyleft?lyright - lyleft:lyleft - lyright,
          rlx = dx?(lxright - lxleft)/dx:0,
          rly = dx?(lyright - lyleft)/dx:0,
          slx = lxright>lxleft?1:-1,
          sly = lyright>lyleft?1:-1,
          ndlx = dlx - (dx?dx*(dlx/dx):0),
          ndly = dly - (dx?dx*(dly/dx):0);
        const float
          pentez = (zright - zleft)/dx,
          pentetx = (txright - txleft)/dx,
          pentety = (tyright - tyleft)/dx;
        int errlx = dx>>1, errly = errlx;
        if (xleft<0 && dx) {
          zleft-=xleft*(zright - zleft)/dx;
          lxleft-=xleft*(lxright - lxleft)/dx;
          lyleft-=xleft*(lyright - lyleft)/dx;
          txleft-=xleft*(txright - txleft)/dx;
          tyleft-=xleft*(tyright - tyleft)/dx;
        }
        if (xleft<0) xleft = 0;
        if (xright>=dimx()-1) xright = dimx()-1;
        T* ptrd = ptr(xleft,y,0,0);
        if (opacity>=1) for (int x = xleft; x<=xright; ++x) {
          const float invz = 1/zleft;
          const tl l = light(lxleft,lyleft);
          const tc *col = texture.ptr((int)(txleft*invz),(int)(tyleft*invz));
          cimg_forV(*this,k) {
            *ptrd = (T)(l<1?l**col:(2-l)**col+(l-1)*maxval);
            ptrd+=whz; col+=twhz;
          }
          ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
          lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0);
          lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0);
        } else for (int x = xleft; x<=xright; ++x) {
          const float invz = 1/zleft;
          const tl l = light(lxleft,lyleft);
          const tc *col = texture.ptr((int)(txleft*invz),(int)(tyleft*invz));
          cimg_forV(*this,k) {
            const T val = (T)(l<1?l**col:(2-l)**col+(l-1)*maxval);
            *ptrd = (T)(nopacity*val + *ptrd*copacity);
            ptrd+=whz; col+=twhz;
          }
          ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
          lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0);
          lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0);
        }
        zr+=pzr; txr+=ptxr; tyr+=ptyr; zl+=pzl; txl+=ptxl; tyl+=ptyl;
      }
      return *this;
    }

    //! Draw a 2D Pseudo-Phong-shaded textured triangle, with z-buffering and perspective correction.
    template<typename tc, typename tl>
    CImg<T>& draw_triangle(float *const zbuffer,
                           const int x0, const int y0, const float z0,
                           const int x1, const int y1, const float z1,
                           const int x2, const int y2, const float z2,
                           const CImg<tc>& texture,
                           const int tx0, const int ty0,
                           const int tx1, const int ty1,
                           const int tx2, const int ty2,
                           const CImg<tl>& light,
                           const int lx0, const int ly0,
                           const int lx1, const int ly1,
                           const int lx2, const int ly2,
                           const float opacity=1) {
      if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this;
      if (!texture || texture.dim<dim)
        throw CImgArgumentException("CImg<%s>::draw_triangle() : Specified texture (%u,%u,%u,%u,%p) is not a valid argument.",
                                    pixel_type(),texture.width,texture.height,texture.depth,texture.dim,texture.data);
      if (!light)
        throw CImgArgumentException("CImg<%s>::draw_triangle() : Specified light texture (%u,%u,%u,%u,%p) is empty.",
                                    pixel_type(),light.width,light.height,light.depth,light.dim,light.data);
      if (is_overlapped(texture)) return draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,
                                                       +texture,tx0,ty0,tx1,ty1,tx2,ty2,light,lx0,ly0,lx1,ly1,lx2,ly2,opacity);
      if (is_overlapped(light)) return draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,
                                                     texture,tx0,ty0,tx1,ty1,tx2,ty2,+light,lx0,ly0,lx1,ly1,lx2,ly2,opacity);
      static const T maxval = (T)cimg::min(cimg::type<T>::max(),cimg::type<tc>::max());
      const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
      const int whz = width*height*depth, twhz = texture.width*texture.height*texture.depth, offx = dim*whz;
      int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2,
        nlx0 = lx0, nly0 = ly0, nlx1 = lx1, nly1 = ly1, nlx2 = lx2, nly2 = ly2;
      float
        ntx0 = tx0/z0, nty0 = ty0/z0,
        ntx1 = tx1/z1, nty1 = ty1/z1,
        ntx2 = tx2/z2, nty2 = ty2/z2,
        nz0 = 1/z0, nz1 = 1/z1, nz2 = 1/z2;
      if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,ntx0,ntx1,nty0,nty1,nlx0,nlx1,nly0,nly1,nz0,nz1);
      if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,ntx0,ntx2,nty0,nty2,nlx0,nlx2,nly0,nly2,nz0,nz2);
      if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,ntx1,ntx2,nty1,nty2,nlx1,nlx2,nly1,nly2,nz1,nz2);
      if (ny0>=dimy() || ny2<0) return *this;
      float
        ptxl = (ntx1 - ntx0)/(ny1 - ny0),
        ptxr = (ntx2 - ntx0)/(ny2 - ny0),
        ptxn = (ntx2 - ntx1)/(ny2 - ny1),
        ptyl = (nty1 - nty0)/(ny1 - ny0),
        ptyr = (nty2 - nty0)/(ny2 - ny0),
        ptyn = (nty2 - nty1)/(ny2 - ny1),
        pzl = (nz1 - nz0)/(ny1 - ny0),
        pzr = (nz2 - nz0)/(ny2 - ny0),
        pzn = (nz2 - nz1)/(ny2 - ny1),
        zr = ny0>=0?nz0:(nz0 - ny0*(nz2 - nz0)/(ny2 - ny0)),
        txr = ny0>=0?ntx0:(ntx0 - ny0*(ntx2 - ntx0)/(ny2 - ny0)),
        tyr = ny0>=0?nty0:(nty0 - ny0*(nty2 - nty0)/(ny2 - ny0)),
        zl = ny1>=0?(ny0>=0?nz0:(nz0 - ny0*(nz1 - nz0)/(ny1 - ny0))):(pzl=pzn,(nz1 - ny1*(nz2 - nz1)/(ny2 - ny1))),
        txl = ny1>=0?(ny0>=0?ntx0:(ntx0 - ny0*(ntx1 - ntx0)/(ny1 - ny0))):(ptxl=ptxn,(ntx1 - ny1*(ntx2 - ntx1)/(ny2 - ny1))),
        tyl = ny1>=0?(ny0>=0?nty0:(nty0 - ny0*(nty1 - nty0)/(ny1 - ny0))):(ptyl=ptyn,(nty1 - ny1*(nty2 - nty1)/(ny2 - ny1)));
      _cimg_for_triangle3(*this,xleft0,lxleft0,lyleft0,xright0,lxright0,lyright0,y,
                          nx0,ny0,nlx0,nly0,nx1,ny1,nlx1,nly1,nx2,ny2,nlx2,nly2) {
        if (y==ny1) { zl = nz1; txl = ntx1; tyl = nty1; pzl = pzn; ptxl = ptxn; ptyl = ptyn; }
        int
          xleft = xleft0, xright = xright0,
          lxleft = lxleft0, lxright = lxright0,
          lyleft = lyleft0, lyright = lyright0;
        float
          zleft = zl, zright = zr,
          txleft = txl, txright = txr,
          tyleft = tyl, tyright = tyr;
        if (xright<xleft) cimg::swap(xleft,xright,zleft,zright,txleft,txright,tyleft,tyright,lxleft,lxright,lyleft,lyright);
        const int
          dx = xright - xleft,
          dlx = lxright>lxleft?lxright - lxleft:lxleft - lxright,
          dly = lyright>lyleft?lyright - lyleft:lyleft - lyright,
          rlx = dx?(lxright - lxleft)/dx:0,
          rly = dx?(lyright - lyleft)/dx:0,
          slx = lxright>lxleft?1:-1,
          sly = lyright>lyleft?1:-1,
          ndlx = dlx - (dx?dx*(dlx/dx):0),
          ndly = dly - (dx?dx*(dly/dx):0);
        const float
          pentez = (zright - zleft)/dx,
          pentetx = (txright - txleft)/dx,
          pentety = (tyright - tyleft)/dx;
        int errlx = dx>>1, errly = errlx;
        if (xleft<0 && dx) {
          zleft-=xleft*(zright - zleft)/dx;
          lxleft-=xleft*(lxright - lxleft)/dx;
          lyleft-=xleft*(lyright - lyleft)/dx;
          txleft-=xleft*(txright - txleft)/dx;
          tyleft-=xleft*(tyright - tyleft)/dx;
        }
        if (xleft<0) xleft = 0;
        if (xright>=dimx()-1) xright = dimx()-1;
        T* ptrd = ptr(xleft,y);
        float *ptrz = zbuffer + xleft + y*width;
        if (opacity>=1) for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) {
          if (zleft>*ptrz) {
            *ptrz = zleft;
            const float invz = 1/zleft;
            const tl l = light(lxleft,lyleft);
            const tc *col = texture.ptr((int)(txleft*invz),(int)(tyleft*invz));
            cimg_forV(*this,k) {
              *ptrd = (T)(l<1?l**col:(2-l)**col+(l-1)*maxval);
              ptrd+=whz; col+=twhz;
            }
            ptrd-=offx;
          }
          zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
          lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0);
          lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0);
        } else for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) {
          if (zleft>*ptrz) {
            *ptrz = zleft;
            const float invz = 1/zleft;
            const tl l = light(lxleft,lyleft);
            const tc *col = texture.ptr((int)(txleft*invz),(int)(tyleft*invz));
            cimg_forV(*this,k) {
              const T val = (T)(l<1?l**col:(2-l)**col+(l-1)*maxval);
              *ptrd = (T)(nopacity*val + *ptrd*copacity);
              ptrd+=whz; col+=twhz;
            }
            ptrd-=offx;
          }
          zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
          lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0);
          lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0);
        }
        zr+=pzr; txr+=ptxr; tyr+=ptyr; zl+=pzl; txl+=ptxl; tyl+=ptyl;
      }
      return *this;
    }

    // Draw a 2D ellipse (inner routine).
    template<typename tc>
    CImg<T>& _draw_ellipse(const int x0, const int y0, const float r1, const float r2, const float ru, const float rv,
                           const tc *const color, const float opacity,
                           const unsigned int pattern) {
      if (is_empty()) return *this;
      if (!color)
        throw CImgArgumentException("CImg<%s>::draw_ellipse : Specified color is (null).",
                                    pixel_type());
      _draw_scanline(color,opacity);
      const float
        nr1 = cimg::abs(r1), nr2 = cimg::abs(r2),
        norm = (float)cimg_std::sqrt(ru*ru+rv*rv),
        u = norm>0?ru/norm:1,
        v = norm>0?rv/norm:0,
        rmax = cimg::max(nr1,nr2),
        l1 = (float)cimg_std::pow(rmax/(nr1>0?nr1:1e-6),2),
        l2 = (float)cimg_std::pow(rmax/(nr2>0?nr2:1e-6),2),
        a = l1*u*u + l2*v*v,
        b = u*v*(l1-l2),
        c = l1*v*v + l2*u*u;
      const int
        yb = (int)cimg_std::sqrt(a*rmax*rmax/(a*c-b*b)),
        tymin = y0 - yb - 1,
        tymax = y0 + yb + 1,
        ymin = tymin<0?0:tymin,
        ymax = tymax>=dimy()?height-1:tymax;
      int oxmin = 0, oxmax = 0;
      bool first_line = true;
      for (int y = ymin; y<=ymax; ++y) {
        const float
          Y = y-y0 + (y<y0?0.5f:-0.5f),
          delta = b*b*Y*Y-a*(c*Y*Y-rmax*rmax),
          sdelta = delta>0?(float)cimg_std::sqrt(delta)/a:0.0f,
          bY = b*Y/a,
          fxmin = x0-0.5f-bY-sdelta,
          fxmax = x0+0.5f-bY+sdelta;
        const int xmin = (int)fxmin, xmax = (int)fxmax;
        if (!pattern) _draw_scanline(xmin,xmax,y,color,opacity);
        else {
          if (first_line) {
            if (y0-yb>=0) _draw_scanline(xmin,xmax,y,color,opacity);
            else draw_point(xmin,y,color,opacity).draw_point(xmax,y,color,opacity);
            first_line = false;
          } else {
            if (xmin<oxmin) _draw_scanline(xmin,oxmin-1,y,color,opacity);
            else _draw_scanline(oxmin+(oxmin==xmin?0:1),xmin,y,color,opacity);
            if (xmax<oxmax) _draw_scanline(xmax,oxmax-1,y,color,opacity);
            else _draw_scanline(oxmax+(oxmax==xmax?0:1),xmax,y,color,opacity);
            if (y==tymax) _draw_scanline(xmin+1,xmax-1,y,color,opacity);
          }
        }
        oxmin = xmin; oxmax = xmax;
      }
      return *this;
    }

    //! Draw a filled ellipse.
    /**
       \param x0 = X-coordinate of the ellipse center.
       \param y0 = Y-coordinate of the ellipse center.
       \param r1 = First radius of the ellipse.
       \param r2 = Second radius of the ellipse.
       \param ru = X-coordinate of the orientation vector related to the first radius.
       \param rv = Y-coordinate of the orientation vector related to the first radius.
       \param color = array of dimv() values of type \c T, defining the drawing color.
       \param opacity = opacity of the drawing.
    **/
    template<typename tc>
    CImg<T>& draw_ellipse(const int x0, const int y0, const float r1, const float r2, const float ru, const float rv,
                          const tc *const color, const float opacity=1) {
      return _draw_ellipse(x0,y0,r1,r2,ru,rv,color,opacity,0U);
    }

    //! Draw a filled ellipse.
    template<typename tc>
    CImg<T>& draw_ellipse(const int x0, const int y0, const float r1, const float r2, const float ru, const float rv,
                          const CImg<tc>& color, const float opacity=1) {
      return draw_ellipse(x0,y0,r1,r2,ru,rv,color.data,opacity);
    }

    //! Draw a filled ellipse.
    /**
       \param x0 = X-coordinate of the ellipse center.
       \param y0 = Y-coordinate of the ellipse center.
       \param tensor = Diffusion tensor describing the ellipse.
       \param color = array of dimv() values of type \c T, defining the drawing color.
       \param opacity = opacity of the drawing.
    **/
    template<typename t, typename tc>
    CImg<T>& draw_ellipse(const int x0, const int y0, const CImg<t> &tensor,
                          const tc *const color, const float opacity=1) {
      CImgList<t> eig = tensor.get_symmetric_eigen();
      const CImg<t> &val = eig[0], &vec = eig[1];
      return draw_ellipse(x0,y0,val(0),val(1),vec(0,0),vec(0,1),color,opacity);
    }

    //! Draw a filled ellipse.
    template<typename t, typename tc>
    CImg<T>& draw_ellipse(const int x0, const int y0, const CImg<t> &tensor,
                          const CImg<tc>& color, const float opacity=1) {
      return draw_ellipse(x0,y0,tensor,color.data,opacity);
    }

    //! Draw an outlined ellipse.
    /**
       \param x0 = X-coordinate of the ellipse center.
       \param y0 = Y-coordinate of the ellipse center.
       \param r1 = First radius of the ellipse.
       \param r2 = Second radius of the ellipse.
       \param ru = X-coordinate of the orientation vector related to the first radius.
       \param rv = Y-coordinate of the orientation vector related to the first radius.
       \param color = array of dimv() values of type \c T, defining the drawing color.
       \param pattern = If zero, the ellipse is filled, else pattern is an integer whose bits describe the outline pattern.
       \param opacity = opacity of the drawing.
    **/
    template<typename tc>
    CImg<T>& draw_ellipse(const int x0, const int y0, const float r1, const float r2, const float ru, const float rv,
                          const tc *const color, const float opacity,
                          const unsigned int pattern) {
      if (pattern) _draw_ellipse(x0,y0,r1,r2,ru,rv,color,opacity,pattern);
      return *this;
    }

    //! Draw an outlined ellipse.
    template<typename tc>
    CImg<T>& draw_ellipse(const int x0, const int y0, const float r1, const float r2, const float ru, const float rv,
                          const CImg<tc>& color, const float opacity,
                          const unsigned int pattern) {
      return draw_ellipse(x0,y0,r1,r2,ru,rv,color.data,opacity,pattern);
    }

    //! Draw an outlined ellipse.
    /**
       \param x0 = X-coordinate of the ellipse center.
       \param y0 = Y-coordinate of the ellipse center.
       \param tensor = Diffusion tensor describing the ellipse.
       \param color = array of dimv() values of type \c T, defining the drawing color.
       \param pattern = If zero, the ellipse is filled, else pattern is an integer whose bits describe the outline pattern.
       \param opacity = opacity of the drawing.
    **/
    template<typename t, typename tc>
    CImg<T>& draw_ellipse(const int x0, const int y0, const CImg<t> &tensor,
                          const tc *const color, const float opacity,
                          const unsigned int pattern) {
      CImgList<t> eig = tensor.get_symmetric_eigen();
      const CImg<t> &val = eig[0], &vec = eig[1];
      return draw_ellipse(x0,y0,val(0),val(1),vec(0,0),vec(0,1),color,opacity,pattern);
    }

    //! Draw an outlined ellipse.
    template<typename t, typename tc>
    CImg<T>& draw_ellipse(const int x0, const int y0, const CImg<t> &tensor,
                          const CImg<tc>& color, const float opacity,
                          const unsigned int pattern) {
      return draw_ellipse(x0,y0,tensor,color.data,opacity,pattern);
    }

    //! Draw a filled circle.
    /**
       \param x0 X-coordinate of the circle center.
       \param y0 Y-coordinate of the circle center.
       \param radius  Circle radius.
       \param color Array of dimv() values of type \c T, defining the drawing color.
       \param opacity Drawing opacity.
       \note
       - Circle version of the Bresenham's algorithm is used.
    **/
    template<typename tc>
    CImg<T>& draw_circle(const int x0, const int y0, int radius,
                         const tc *const color, const float opacity=1) {
      if (!is_empty()) {
        if (!color)
          throw CImgArgumentException("CImg<%s>::draw_circle : Specified color is (null).",
                                      pixel_type());
        _draw_scanline(color,opacity);
        if (radius<0 || x0-radius>=dimx() || y0+radius<0 || y0-radius>=dimy()) return *this;
        if (y0>=0 && y0<dimy()) _draw_scanline(x0-radius,x0+radius,y0,color,opacity);
        for (int f=1-radius, ddFx=0, ddFy=-(radius<<1), x=0, y=radius; x<y; ) {
          if (f>=0) {
            const int x1 = x0-x, x2 = x0+x, y1 = y0-y, y2 = y0+y;
            if (y1>=0 && y1<dimy()) _draw_scanline(x1,x2,y1,color,opacity);
            if (y2>=0 && y2<dimy()) _draw_scanline(x1,x2,y2,color,opacity);
            f+=(ddFy+=2); --y;
          }
          const bool no_diag = y!=(x++);
          ++(f+=(ddFx+=2));
          const int x1 = x0-y, x2 = x0+y, y1 = y0-x, y2 = y0+x;
          if (no_diag) {
            if (y1>=0 && y1<dimy()) _draw_scanline(x1,x2,y1,color,opacity);
            if (y2>=0 && y2<dimy()) _draw_scanline(x1,x2,y2,color,opacity);
          }
        }
      }
      return *this;
    }

    //! Draw a filled circle.
    template<typename tc>
    CImg<T>& draw_circle(const int x0, const int y0, int radius,
                         const CImg<tc>& color, const float opacity=1) {
      return draw_circle(x0,y0,radius,color.data,opacity);
    }

    //! Draw an outlined circle.
    /**
       \param x0 X-coordinate of the circle center.
       \param y0 Y-coordinate of the circle center.
       \param radius Circle radius.
       \param color Array of dimv() values of type \c T, defining the drawing color.
       \param opacity Drawing opacity.
    **/
    template<typename tc>
    CImg<T>& draw_circle(const int x0, const int y0, int radius,
                         const tc *const color, const float opacity,
                         const unsigned int) {
      if (!is_empty()) {
        if (!color)
          throw CImgArgumentException("CImg<%s>::draw_circle : Specified color is (null).",
                                      pixel_type());
        if (radius<0 || x0-radius>=dimx() || y0+radius<0 || y0-radius>=dimy()) return *this;
        if (!radius) return draw_point(x0,y0,color,opacity);
        draw_point(x0-radius,y0,color,opacity).draw_point(x0+radius,y0,color,opacity).
          draw_point(x0,y0-radius,color,opacity).draw_point(x0,y0+radius,color,opacity);
        if (radius==1) return *this;
        for (int f=1-radius, ddFx=0, ddFy=-(radius<<1), x=0, y=radius; x<y; ) {
          if (f>=0) { f+=(ddFy+=2); --y; }
          ++x; ++(f+=(ddFx+=2));
          if (x!=y+1) {
            const int x1 = x0-y, x2 = x0+y, y1 = y0-x, y2 = y0+x, x3 = x0-x, x4 = x0+x, y3 = y0-y, y4 = y0+y;
            draw_point(x1,y1,color,opacity).draw_point(x1,y2,color,opacity).
              draw_point(x2,y1,color,opacity).draw_point(x2,y2,color,opacity);
            if (x!=y)
              draw_point(x3,y3,color,opacity).draw_point(x4,y4,color,opacity).
                draw_point(x4,y3,color,opacity).draw_point(x3,y4,color,opacity);
          }
        }
      }
      return *this;
    }

    //! Draw an outlined circle.
    template<typename tc>
    CImg<T>& draw_circle(const int x0, const int y0, int radius, const CImg<tc>& color,
                         const float opacity,
                         const unsigned int pattern) {
      return draw_circle(x0,y0,radius,color.data,opacity,pattern);
    }

    // Draw a text (internal).
    template<typename tc1, typename tc2, typename t>
    CImg<T>& _draw_text(const int x0, const int y0, const char *const text,
                        const tc1 *const foreground_color, const tc2 *const background_color,
                        const float opacity, const CImgList<t>& font) {
      if (!text) return *this;
      if (!font)
        throw CImgArgumentException("CImg<%s>::draw_text() : Specified font (%u,%p) is empty.",
                                    pixel_type(),font.size,font.data);
      const int text_length = cimg::strlen(text);

      if (is_empty()) {
        // If needed, pre-compute necessary size of the image
        int x = 0, y = 0, w = 0;
        unsigned char c = 0;
        for (int i = 0; i<text_length; ++i) {
          c = text[i];
          switch (c) {
          case '\n' : y+=font[' '].height; if (x>w) w = x; x = 0; break;
          case '\t' : x+=4*font[' '].width; break;
          default : if (c<font.size) x+=font[c].width;
          }
        }
        if (x!=0 || c=='\n') {
          if (x>w) w=x;
          y+=font[' '].height;
        }
        assign(x0+w,y0+y,1,font[' '].dim,0);
        if (background_color) cimg_forV(*this,k) get_shared_channel(k).fill((T)background_color[k]);
      }

      int x = x0, y = y0;
      CImg<T> letter;
      for (int i = 0; i<text_length; ++i) {
        const unsigned char c = text[i];
        switch (c) {
        case '\n' : y+=font[' '].height; x = x0; break;
        case '\t' : x+=4*font[' '].width; break;
        default : if (c<font.size) {
          letter = font[c];
          const CImg<T>& mask = (c+256)<(int)font.size?font[c+256]:font[c];
          if (foreground_color) for (unsigned int p = 0; p<letter.width*letter.height; ++p)
            if (mask(p)) cimg_forV(*this,k) letter(p,0,0,k) = (T)(letter(p,0,0,k)*foreground_color[k]);
          if (background_color) for (unsigned int p = 0; p<letter.width*letter.height; ++p)
            if (!mask(p)) cimg_forV(*this,k) letter(p,0,0,k) = (T)background_color[k];
          if (!background_color && font.size>=512) draw_image(x,y,letter,mask,opacity,(T)1);
          else draw_image(x,y,letter,opacity);
          x+=letter.width;
        }
        }
      }
      return *this;
    }

    //! Draw a text.
    /**
       \param x0 X-coordinate of the text in the instance image.
       \param y0 Y-coordinate of the text in the instance image.
       \param foreground_color Array of dimv() values of type \c T, defining the foreground color (0 means 'transparent').
       \param background_color Array of dimv() values of type \c T, defining the background color (0 means 'transparent').
       \param font Font used for drawing text.
       \param opacity Drawing opacity.
       \param format 'printf'-style format string, followed by arguments.
       \note Clipping is supported.
    **/
    template<typename tc1, typename tc2, typename t>
    CImg<T>& draw_text(const int x0, const int y0, const char *const text,
                       const tc1 *const foreground_color, const tc2 *const background_color,
                       const float opacity, const CImgList<t>& font, ...) {
      char tmp[2048] = { 0 }; cimg_std::va_list ap; va_start(ap,font);
      cimg_std::vsprintf(tmp,text,ap); va_end(ap);
      return _draw_text(x0,y0,tmp,foreground_color,background_color,opacity,font);
    }

    //! Draw a text.
    template<typename tc1, typename tc2, typename t>
    CImg<T>& draw_text(const int x0, const int y0, const char *const text,
                       const CImg<tc1>& foreground_color, const CImg<tc2>& background_color,
                       const float opacity, const CImgList<t>& font, ...) {
      char tmp[2048] = { 0 }; cimg_std::va_list ap; va_start(ap,font);
      cimg_std::vsprintf(tmp,text,ap); va_end(ap);
      return _draw_text(x0,y0,tmp,foreground_color,background_color,opacity,font);
    }

    //! Draw a text.
    template<typename tc, typename t>
    CImg<T>& draw_text(const int x0, const int y0, const char *const text,
                       const tc *const foreground_color, const int background_color,
                       const float opacity, const CImgList<t>& font, ...) {
      char tmp[2048] = { 0 }; cimg_std::va_list ap; va_start(ap,font);
      cimg_std::vsprintf(tmp,text,ap); va_end(ap);
      return _draw_text(x0,y0,tmp,foreground_color,(tc*)background_color,opacity,font);
    }

    //! Draw a text.
    template<typename tc, typename t>
    CImg<T>& draw_text(const int x0, const int y0, const char *const text,
                       const int foreground_color, const tc *const background_color,
                       const float opacity, const CImgList<t>& font, ...) {
      char tmp[2048] = { 0 }; cimg_std::va_list ap; va_start(ap,font);
      cimg_std::vsprintf(tmp,text,ap); va_end(ap);
      return _draw_text(x0,y0,tmp,(tc*)foreground_color,background_color,opacity,font);
    }

    //! Draw a text.
    /**
       \param x0 X-coordinate of the text in the instance image.
       \param y0 Y-coordinate of the text in the instance image.
       \param foreground_color Array of dimv() values of type \c T, defining the foreground color (0 means 'transparent').
       \param background_color Array of dimv() values of type \c T, defining the background color (0 means 'transparent').
       \param font_size Size of the font (nearest match).
       \param opacity Drawing opacity.
       \param format 'printf'-style format string, followed by arguments.
       \note Clipping is supported.
    **/
    template<typename tc1, typename tc2>
    CImg<T>& draw_text(const int x0, const int y0, const char *const text,
                       const tc1 *const foreground_color, const tc2 *const background_color,
                       const float opacity=1, const unsigned int font_size=11, ...) {
      static CImgList<T> font;
      static unsigned int fsize = 0;
      if (fsize!=font_size) { font = CImgList<T>::font(font_size); fsize = font_size; }
      char tmp[2048] = { 0 }; cimg_std::va_list ap; va_start(ap,font_size); cimg_std::vsprintf(tmp,text,ap); va_end(ap);
      return _draw_text(x0,y0,tmp,foreground_color,background_color,opacity,font);
    }

    //! Draw a text.
    template<typename tc1, typename tc2>
    CImg<T>& draw_text(const int x0, const int y0, const char *const text,
                       const CImg<tc1>& foreground_color, const CImg<tc2>& background_color,
                       const float opacity=1, const unsigned int font_size=11, ...) {
      static CImgList<T> font;
      static unsigned int fsize = 0;
      if (fsize!=font_size) { font = CImgList<T>::font(font_size); fsize = font_size; }
      char tmp[2048] = { 0 }; cimg_std::va_list ap; va_start(ap,font_size); cimg_std::vsprintf(tmp,text,ap); va_end(ap);
      return _draw_text(x0,y0,tmp,foreground_color.data,background_color.data,opacity,font);
    }

    //! Draw a text.
    template<typename tc>
    CImg<T>& draw_text(const int x0, const int y0, const char *const text,
                       const tc *const foreground_color, const int background_color=0,
                       const float opacity=1, const unsigned int font_size=11, ...) {
      static CImgList<T> font;
      static unsigned int fsize = 0;
      if (fsize!=font_size) { font = CImgList<T>::font(font_size); fsize = font_size; }
      char tmp[2048] = { 0 }; cimg_std::va_list ap; va_start(ap,font_size); cimg_std::vsprintf(tmp,text,ap); va_end(ap);
      return _draw_text(x0,y0,tmp,foreground_color,(const tc*)background_color,opacity,font);
    }

    //! Draw a text.
    template<typename tc>
    CImg<T>& draw_text(const int x0, const int y0, const char *const text,
                       const int foreground_color, const tc *const background_color,
                       const float opacity=1, const unsigned int font_size=11, ...) {
      static CImgList<T> font;
      static unsigned int fsize = 0;
      if (fsize!=font_size) { font = CImgList<T>::font(font_size); fsize = font_size; }
      char tmp[2048] = { 0 }; cimg_std::va_list ap; va_start(ap,font_size); cimg_std::vsprintf(tmp,text,ap); va_end(ap);
      return _draw_text(x0,y0,tmp,(tc*)foreground_color,background_color,opacity,font);
    }

    //! Draw a vector field in the instance image, using a colormap.
    /**
       \param flow Image of 2d vectors used as input data.
       \param color Image of dimv()-D vectors corresponding to the color of each arrow.
       \param sampling Length (in pixels) between each arrow.
       \param factor Length factor of each arrow (if <0, computed as a percentage of the maximum length).
       \param quiver_type Type of plot. Can be 0 (arrows) or 1 (segments).
       \param opacity Opacity of the drawing.
       \param pattern Used pattern to draw lines.
       \note Clipping is supported.
    **/
    template<typename t1, typename t2>
    CImg<T>& draw_quiver(const CImg<t1>& flow,
                         const t2 *const color, const float opacity=1,
                         const unsigned int sampling=25, const float factor=-20,
                         const int quiver_type=0, const unsigned int pattern=~0U) {
      return draw_quiver(flow,CImg<t2>(color,dim,1,1,1,true),opacity,sampling,factor,quiver_type,pattern);
    }

    //! Draw a vector field in the instance image, using a colormap.
    /**
       \param flow Image of 2d vectors used as input data.
       \param color Image of dimv()-D vectors corresponding to the color of each arrow.
       \param sampling Length (in pixels) between each arrow.
       \param factor Length factor of each arrow (if <0, computed as a percentage of the maximum length).
       \param quiver_type Type of plot. Can be 0 (arrows) or 1 (segments).
       \param opacity Opacity of the drawing.
       \param pattern Used pattern to draw lines.
       \note Clipping is supported.
    **/
    template<typename t1, typename t2>
    CImg<T>& draw_quiver(const CImg<t1>& flow,
                         const CImg<t2>& color, const float opacity=1,
                         const unsigned int sampling=25, const float factor=-20,
                         const int quiver_type=0, const unsigned int pattern=~0U) {
      if (!is_empty()) {
        if (!flow || flow.dim!=2)
          throw CImgArgumentException("CImg<%s>::draw_quiver() : Specified flow (%u,%u,%u,%u,%p) has wrong dimensions.",
                                      pixel_type(),flow.width,flow.height,flow.depth,flow.dim,flow.data);
        if (sampling<=0)
          throw CImgArgumentException("CImg<%s>::draw_quiver() : Incorrect sampling value = %g",
                                      pixel_type(),sampling);
        const bool colorfield = (color.width==flow.width && color.height==flow.height && color.depth==1 && color.dim==dim);
        if (is_overlapped(flow)) return draw_quiver(+flow,color,opacity,sampling,factor,quiver_type,pattern);

        float vmax,fact;
        if (factor<=0) {
          float m, M = (float)flow.get_pointwise_norm(2).maxmin(m);
          vmax = (float)cimg::max(cimg::abs(m),cimg::abs(M));
          fact = -factor;
        } else { fact = factor; vmax = 1; }

        for (unsigned int y=sampling/2; y<height; y+=sampling)
          for (unsigned int x=sampling/2; x<width; x+=sampling) {
            const unsigned int X = x*flow.width/width, Y = y*flow.height/height;
            float u = (float)flow(X,Y,0,0)*fact/vmax, v = (float)flow(X,Y,0,1)*fact/vmax;
            if (!quiver_type) {
              const int xx = x+(int)u, yy = y+(int)v;
              if (colorfield) draw_arrow(x,y,xx,yy,color.get_vector_at(X,Y).data,opacity,45,sampling/5.0f,pattern);
              else draw_arrow(x,y,xx,yy,color,opacity,45,sampling/5.0f,pattern);
            } else {
              if (colorfield) draw_line((int)(x-0.5*u),(int)(y-0.5*v),(int)(x+0.5*u),(int)(y+0.5*v),color.get_vector_at(X,Y),opacity,pattern);
              else draw_line((int)(x-0.5*u),(int)(y-0.5*v),(int)(x+0.5*u),(int)(y+0.5*v),color,opacity,pattern);
            }
          }
      }
      return *this;
    }

    //! Draw a 1D graph on the instance image.
    /**
       \param data Image containing the graph values I = f(x).
       \param color Array of dimv() values of type \c T, defining the drawing color.
       \param gtype Define the type of the plot :
                      - 0 = Plot using points clouds.
                      - 1 = Plot using linear interpolation (segments).
                      - 2 = Plot with bars.
                      - 3 = Plot using cubic interpolation (3-polynomials).
                      - 4 = Plot using cross clouds.
       \param ymin Lower bound of the y-range.
       \param ymax Upper bound of the y-range.
       \param opacity Drawing opacity.
       \param pattern Drawing pattern.
       \note
         - if \c ymin==ymax==0, the y-range is computed automatically from the input sample.
    **/
    template<typename t, typename tc>
    CImg<T>& draw_graph(const CImg<t>& data,
                        const tc *const color, const float opacity=1,
                        const unsigned int plot_type=1, const unsigned int vertex_type=1,
                        const double ymin=0, const double ymax=0,
                        const unsigned int pattern=~0U) {
      if (is_empty() || height<=1) return *this;;
      const unsigned long siz = data.size();
      if (!color)
        throw CImgArgumentException("CImg<%s>::draw_graph() : Specified color is (null)",
                                    pixel_type());
      tc *color1 = 0, *color2 = 0;
      if (plot_type==3) {
        color1 = new tc[dim]; color2 = new tc[dim];
        cimg_forV(*this,k) { color1[k] = (tc)(color[k]*0.6f); color2[k] = (tc)(color[k]*0.3f); }
      }

      double m = ymin, M = ymax;
      if (ymin==ymax) m = (double)data.maxmin(M);
      if (m==M) { --m; ++M; }
      const float ca = (float)(M-m)/(height-1);
      bool init_hatch = true;

      // Draw graph edges
      switch (plot_type%4) {
      case 1 : { // Segments
        int oX = 0, oY = (int)((data[0]-m)/ca);
        for (unsigned long off = 1; off<siz; ++off) {
          const int
            X = (int)(off*width/siz),
            Y = (int)((data[off]-m)/ca);
          draw_line(oX,oY,X,Y,color,opacity,pattern,init_hatch);
          oX = X; oY = Y;
          init_hatch = false;
        }
      } break;
      case 2 : { // Spline
        const CImg<t> ndata = data.get_shared_points(0,siz-1);
        int oY = (int)((data[0]-m)/ca);
        cimg_forX(*this,x) {
          const int Y = (int)((ndata._cubic_atX((float)x*ndata.width/width)-m)/ca);
          if (x>0) draw_line(x,oY,x+1,Y,color,opacity,pattern,init_hatch);
          init_hatch = false;
          oY = Y;
        }
      } break;
      case 3 : { // Bars
        const int Y0 = (int)(-m/ca);
        int oX = 0;
        cimg_foroff(data,off) {
          const int
            X = (off+1)*width/siz-1,
            Y = (int)((data[off]-m)/ca);
          draw_rectangle(oX,Y0,X,Y,color1,opacity).
            draw_line(oX,Y,oX,Y0,color2,opacity).
            draw_line(oX,Y0,X,Y0,Y<=Y0?color2:color,opacity).
            draw_line(X,Y,X,Y0,color,opacity).
            draw_line(oX,Y,X,Y,Y<=Y0?color:color2,opacity);
          oX = X+1;
        }
      } break;
      default : break; // No edges
      }

      // Draw graph points
      switch (vertex_type%8) {
      case 1 : { // Point
        cimg_foroff(data,off) {
          const int X = off*width/siz, Y = (int)((data[off]-m)/ca);
          draw_point(X,Y,color,opacity);
        }
      } break;
      case 2 : { // Standard Cross
        cimg_foroff(data,off) {
          const int X = off*width/siz, Y = (int)((data[off]-m)/ca);
          draw_line(X-3,Y,X+3,Y,color,opacity).draw_line(X,Y-3,X,Y+3,color,opacity);
        }
      } break;
      case 3 : { // Rotated Cross
        cimg_foroff(data,off) {
          const int X = off*width/siz, Y = (int)((data[off]-m)/ca);
          draw_line(X-3,Y-3,X+3,Y+3,color,opacity).draw_line(X-3,Y+3,X+3,Y-3,color,opacity);
        }
      } break;
      case 4 : { // Filled Circle
        cimg_foroff(data,off) {
          const int X = off*width/siz, Y = (int)((data[off]-m)/ca);
          draw_circle(X,Y,3,color,opacity);
        }
      } break;
      case 5 : { // Outlined circle
        cimg_foroff(data,off) {
          const int X = off*width/siz, Y = (int)((data[off]-m)/ca);
          draw_circle(X,Y,3,color,opacity,0U);
        }
      } break;
      case 6 : { // Square
        cimg_foroff(data,off) {
          const int X = off*width/siz, Y = (int)((data[off]-m)/ca);
          draw_rectangle(X-3,Y-3,X+3,Y+3,color,opacity,~0U);
        }
      } break;
      case 7 : { // Diamond
        cimg_foroff(data,off) {
          const int X = off*width/siz, Y = (int)((data[off]-m)/ca);
          draw_line(X,Y-4,X+4,Y,color,opacity).
            draw_line(X+4,Y,X,Y+4,color,opacity).
            draw_line(X,Y+4,X-4,Y,color,opacity).
            draw_line(X-4,Y,X,Y-4,color,opacity);
        }
      } break;
      default : break; // No vertices
      }

      if (color1) delete[] color1; if (color2) delete[] color2;
      return *this;
    }

    //! Draw a 1D graph on the instance image.
    template<typename t, typename tc>
    CImg<T>& draw_graph(const CImg<t>& data,
                        const CImg<tc>& color, const float opacity=1,
                        const unsigned int plot_type=1, const unsigned int vertex_type=1,
                        const double ymin=0, const double ymax=0,
                        const unsigned int pattern=~0U) {
      return draw_graph(data,color.data,opacity,plot_type,vertex_type,ymin,ymax,pattern);
    }

    //! Draw a labeled horizontal axis on the instance image.
    /**
       \param xvalues Lower bound of the x-range.
       \param y Y-coordinate of the horizontal axis in the instance image.
       \param color Array of dimv() values of type \c T, defining the drawing color.
       \param opacity Drawing opacity.
       \param pattern Drawing pattern.
       \param opacity_out Drawing opacity of 'outside' axes.
       \note if \c precision==0, precision of the labels is automatically computed.
    **/
    template<typename t, typename tc>
    CImg<T>& draw_axis(const CImg<t>& xvalues, const int y,
                       const tc *const color, const float opacity=1,
                       const unsigned int pattern=~0U) {
      if (!is_empty()) {
        int siz = (int)xvalues.size()-1;
        if (siz<=0) draw_line(0,y,width-1,y,color,opacity,pattern);
        else {
          if (xvalues[0]<xvalues[siz]) draw_arrow(0,y,width-1,y,color,opacity,30,5,pattern);
          else draw_arrow(width-1,y,0,y,color,opacity,30,5,pattern);
          const int yt = (y+14)<dimy()?(y+3):(y-14);
          char txt[32];
          cimg_foroff(xvalues,x) {
            cimg_std::sprintf(txt,"%g",(double)xvalues(x));
            const int xi = (int)(x*(width-1)/siz), xt = xi-(int)cimg::strlen(txt)*3;
            draw_point(xi,y-1,color,opacity).draw_point(xi,y+1,color,opacity).
              draw_text(xt<0?0:xt,yt,txt,color,(tc*)0,opacity,11);
          }
        }
      }
      return *this;
    }

    //! Draw a labeled horizontal axis on the instance image.
    template<typename t, typename tc>
    CImg<T>& draw_axis(const CImg<t>& xvalues, const int y,
                       const CImg<tc>& color, const float opacity=1,
                       const unsigned int pattern=~0U) {
      return draw_axis(xvalues,y,color.data,opacity,pattern);
    }

    //! Draw a labeled vertical axis on the instance image.
    template<typename t, typename tc>
    CImg<T>& draw_axis(const int x, const CImg<t>& yvalues,
                       const tc *const color, const float opacity=1,
                       const unsigned int pattern=~0U) {
      if (!is_empty()) {
        int siz = (int)yvalues.size()-1;
        if (siz<=0) draw_line(x,0,x,height-1,color,opacity,pattern);
        else {
          if (yvalues[0]<yvalues[siz]) draw_arrow(x,0,x,height-1,color,opacity,30,5,pattern);
          else draw_arrow(x,height-1,x,0,color,opacity,30,5,pattern);
          char txt[32];
          cimg_foroff(yvalues,y) {
            cimg_std::sprintf(txt,"%g",(double)yvalues(y));
            const int
              yi = (int)(y*(height-1)/siz),
              tmp = yi-5,
              nyi = tmp<0?0:(tmp>=dimy()-11?dimy()-11:tmp),
              xt = x-(int)cimg::strlen(txt)*7;
            draw_point(x-1,yi,color,opacity).draw_point(x+1,yi,color,opacity);
            if (xt>0) draw_text(xt,nyi,txt,color,(tc*)0,opacity,11);
            else draw_text(x+3,nyi,txt,color,(tc*)0,opacity,11);
          }
        }
      }
      return *this;
    }

    //! Draw a labeled vertical axis on the instance image.
    template<typename t, typename tc>
    CImg<T>& draw_axis(const int x, const CImg<t>& yvalues,
                       const CImg<tc>& color, const float opacity=1,
                       const unsigned int pattern=~0U) {
      return draw_axis(x,yvalues,color.data,opacity,pattern);
    }

    //! Draw a labeled horizontal+vertical axis on the instance image.
    template<typename tx, typename ty, typename tc>
      CImg<T>& draw_axis(const CImg<tx>& xvalues, const CImg<ty>& yvalues,
                         const tc *const color, const float opacity=1,
                         const unsigned int patternx=~0U, const unsigned int patterny=~0U) {
      if (!is_empty()) {
        const CImg<tx> nxvalues(xvalues.data,xvalues.size(),1,1,1,true);
        const int sizx = (int)xvalues.size()-1, wm1 = (int)(width)-1;
        if (sizx>0) {
          float ox = (float)nxvalues[0];
          for (unsigned int x = 1; x<width; ++x) {
            const float nx = (float)nxvalues._linear_atX((float)x*sizx/wm1);
            if (nx*ox<=0) { draw_axis(nx==0?x:x-1,yvalues,color,opacity,patterny); break; }
            ox = nx;
          }
        }
        const CImg<ty> nyvalues(yvalues.data,yvalues.size(),1,1,1,true);
        const int sizy = (int)yvalues.size()-1, hm1 = (int)(height)-1;
        if (sizy>0) {
          float oy = (float)nyvalues[0];
          for (unsigned int y = 1; y<height; ++y) {
            const float ny = (float)nyvalues._linear_atX((float)y*sizy/hm1);
            if (ny*oy<=0) { draw_axis(xvalues,ny==0?y:y-1,color,opacity,patternx); break; }
            oy = ny;
          }
        }
      }
      return *this;
    }

    //! Draw a labeled horizontal+vertical axis on the instance image.
    template<typename tx, typename ty, typename tc>
    CImg<T>& draw_axis(const CImg<tx>& xvalues, const CImg<ty>& yvalues,
                       const CImg<tc>& color, const float opacity=1,
                       const unsigned int patternx=~0U, const unsigned int patterny=~0U) {
      return draw_axis(xvalues,yvalues,color.data,opacity,patternx,patterny);
    }

    //! Draw a labeled horizontal+vertical axis on the instance image.
    template<typename tc>
    CImg<T>& draw_axis(const float x0, const float x1, const float y0, const float y1,
                       const tc *const color, const float opacity=1,
                       const int subdivisionx=-60, const int subdivisiony=-60,
                       const float precisionx=0, const float precisiony=0,
                       const unsigned int patternx=~0U, const unsigned int patterny=~0U) {
      if (!is_empty()) {
        const float
          dx = cimg::abs(x1-x0), dy = cimg::abs(y1-y0),
          px = (precisionx==0)?(float)cimg_std::pow(10.0,(int)cimg_std::log10(dx)-2.0):precisionx,
          py = (precisiony==0)?(float)cimg_std::pow(10.0,(int)cimg_std::log10(dy)-2.0):precisiony;
        draw_axis(CImg<floatT>::sequence(subdivisionx>0?subdivisionx:1-dimx()/subdivisionx,x0,x1).round(px),
                  CImg<floatT>::sequence(subdivisiony>0?subdivisiony:1-dimy()/subdivisiony,y0,y1).round(py),
                  color,opacity,patternx,patterny);
      }
      return *this;
    }

    //! Draw a labeled horizontal+vertical axis on the instance image.
    template<typename tc>
    CImg<T>& draw_axis(const float x0, const float x1, const float y0, const float y1,
                       const CImg<tc>& color, const float opacity=1,
                       const int subdivisionx=-60, const int subdivisiony=-60,
                       const float precisionx=0, const float precisiony=0,
                       const unsigned int patternx=~0U, const unsigned int patterny=~0U) {
      return draw_axis(x0,x1,y0,y1,color.data,opacity,subdivisionx,subdivisiony,precisionx,precisiony,patternx,patterny);
    }

    //! Draw grid.
    template<typename tx, typename ty, typename tc>
    CImg<T>& draw_grid(const CImg<tx>& xvalues, const CImg<ty>& yvalues,
                       const tc *const color, const float opacity=1,
                       const unsigned int patternx=~0U, const unsigned int patterny=~0U) {
      if (!is_empty()) {
        if (xvalues) cimg_foroff(xvalues,x) {
          const int xi = (int)xvalues[x];
          if (xi>=0 && xi<dimx()) draw_line(xi,0,xi,height-1,color,opacity,patternx);
        }
        if (yvalues) cimg_foroff(yvalues,y) {
          const int yi = (int)yvalues[y];
          if (yi>=0 && yi<dimy()) draw_line(0,yi,width-1,yi,color,opacity,patterny);
        }
      }
      return *this;
    }

    //! Draw grid.
    template<typename tx, typename ty, typename tc>
    CImg<T>& draw_grid(const CImg<tx>& xvalues, const CImg<ty>& yvalues,
                       const CImg<tc>& color, const float opacity=1,
                       const unsigned int patternx=~0U, const unsigned int patterny=~0U) {
      return draw_grid(xvalues,yvalues,color.data,opacity,patternx,patterny);
    }

    //! Draw grid.
    template<typename tc>
    CImg<T>& draw_grid(const float deltax,  const float deltay,
                       const float offsetx, const float offsety,
                       const bool invertx, const bool inverty,
                       const tc *const color, const float opacity=1,
                       const unsigned int patternx=~0U, const unsigned int patterny=~0U) {
      CImg<uintT> seqx, seqy;
      if (deltax!=0) {
        const float dx = deltax>0?deltax:width*-deltax/100;
        const unsigned int nx = (unsigned int)(width/dx);
        seqx = CImg<uintT>::sequence(1+nx,0,(unsigned int)(dx*nx));
        if (offsetx) cimg_foroff(seqx,x) seqx(x) = (unsigned int)cimg::mod(seqx(x)+offsetx,(float)width);
        if (invertx) cimg_foroff(seqx,x) seqx(x) = width-1-seqx(x);
      }

      if (deltay!=0) {
        const float dy = deltay>0?deltay:height*-deltay/100;
        const unsigned int ny = (unsigned int)(height/dy);
        seqy = CImg<uintT>::sequence(1+ny,0,(unsigned int)(dy*ny));
        if (offsety) cimg_foroff(seqy,y) seqy(y) = (unsigned int)cimg::mod(seqy(y)+offsety,(float)height);
        if (inverty) cimg_foroff(seqy,y) seqy(y) = height-1-seqy(y);
     }
      return draw_grid(seqx,seqy,color,opacity,patternx,patterny);
    }

    //! Draw grid.
    template<typename tc>
    CImg<T>& draw_grid(const float deltax,  const float deltay,
                       const float offsetx, const float offsety,
                       const bool invertx, const bool inverty,
                       const CImg<tc>& color, const float opacity=1,
                       const unsigned int patternx=~0U, const unsigned int patterny=~0U) {
      return draw_grid(deltax,deltay,offsetx,offsety,invertx,inverty,color.data,opacity,patternx,patterny);
    }

    //! Draw a 3D filled region starting from a point (\c x,\c y,\ z) in the instance image.
    /**
       \param x X-coordinate of the starting point of the region to fill.
       \param y Y-coordinate of the starting point of the region to fill.
       \param z Z-coordinate of the starting point of the region to fill.
       \param color An array of dimv() values of type \c T, defining the drawing color.
       \param region Image that will contain the mask of the filled region mask, as an output.
       \param sigma Tolerance concerning neighborhood values.
       \param opacity Opacity of the drawing.
       \param high_connexity Tells if 8-connexity must be used (only for 2D images).
       \return \p region is initialized with the binary mask of the filled region.
    **/
    template<typename tc, typename t>
    CImg<T>& draw_fill(const int x, const int y, const int z,
                       const tc *const color, const float opacity,
                       CImg<t>& region, const float sigma=0,
                       const bool high_connexity=false) {

#define _cimg_draw_fill_test(x,y,z,res) if (region(x,y,z)) res = false; else { \
  res = true; \
  const T *reference_col = reference_color.ptr() + dim, *ptrs = ptr(x,y,z) + siz; \
  for (unsigned int i = dim; res && i; --i) { ptrs-=whz; res = (cimg::abs(*ptrs - *(--reference_col))<=sigma); } \
  region(x,y,z) = (t)(res?1:noregion); \
}

#define _cimg_draw_fill_set(x,y,z) { \
  const tc *col = color; \
  T *ptrd = ptr(x,y,z); \
  if (opacity>=1) cimg_forV(*this,k) { *ptrd = (T)*(col++); ptrd+=whz; } \
  else cimg_forV(*this,k) { *ptrd = (T)(*(col++)*nopacity + *ptrd*copacity); ptrd+=whz; } \
}

#define _cimg_draw_fill_insert(x,y,z) { \
  if (posr1>=remaining.height) remaining.resize(3,remaining.height<<1,1,1,0); \
  unsigned int *ptrr = remaining.ptr(0,posr1); \
  *(ptrr++) = x; *(ptrr++) = y; *(ptrr++) = z; ++posr1; \
}

#define _cimg_draw_fill_test_neighbor(x,y,z,cond) if (cond) { \
  const unsigned int tx = x, ty = y, tz = z; \
  _cimg_draw_fill_test(tx,ty,tz,res); if (res) _cimg_draw_fill_insert(tx,ty,tz); \
}

      if (!color)
        throw CImgArgumentException("CImg<%s>::draw_fill() : Specified color is (null).",
                                    pixel_type());
      region.assign(width,height,depth,1,(t)0);
      if (x>=0 && x<dimx() && y>=0 && y<dimy() && z>=0 && z<dimz()) {
        const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
        const unsigned int whz = width*height*depth, siz = dim*whz, W1 = width-1, H1 = height-1, D1 = depth-1;
        const bool threed = depth>1;
        const CImg<T> reference_color = get_vector_at(x,y,z);
        CImg<uintT> remaining(3,512,1,1,0);
        remaining(0,0) = x; remaining(1,0) = y; remaining(2,0) = z;
        unsigned int posr0 = 0, posr1 = 1;
        region(x,y,z) = (t)1;
        const t noregion = ((t)1==(t)2)?(t)0:(t)(-1);
        if (threed) do { // 3D version of the filling algorithm
          const unsigned int *pcurr = remaining.ptr(0,posr0++), xc = *(pcurr++), yc = *(pcurr++), zc = *(pcurr++);
          if (posr0>=512) { remaining.translate(0,posr0); posr1-=posr0; posr0 = 0; }
          bool cont, res;
          unsigned int nxc = xc;
          do { // X-backward
            _cimg_draw_fill_set(nxc,yc,zc);
            _cimg_draw_fill_test_neighbor(nxc,yc-1,zc,yc!=0);
            _cimg_draw_fill_test_neighbor(nxc,yc+1,zc,yc<H1);
            _cimg_draw_fill_test_neighbor(nxc,yc,zc-1,zc!=0);
            _cimg_draw_fill_test_neighbor(nxc,yc,zc+1,zc<D1);
            if (nxc) { --nxc; _cimg_draw_fill_test(nxc,yc,zc,cont); } else cont = false;
          } while (cont);
          nxc = xc;
          do { // X-forward
            if ((++nxc)<=W1) { _cimg_draw_fill_test(nxc,yc,zc,cont); } else cont = false;
            if (cont) {
              _cimg_draw_fill_set(nxc,yc,zc);
              _cimg_draw_fill_test_neighbor(nxc,yc-1,zc,yc!=0);
              _cimg_draw_fill_test_neighbor(nxc,yc+1,zc,yc<H1);
              _cimg_draw_fill_test_neighbor(nxc,yc,zc-1,zc!=0);
              _cimg_draw_fill_test_neighbor(nxc,yc,zc+1,zc<D1);
            }
          } while (cont);
          unsigned int nyc = yc;
          do { // Y-backward
            if (nyc) { --nyc; _cimg_draw_fill_test(xc,nyc,zc,cont); } else cont = false;
            if (cont) {
              _cimg_draw_fill_set(xc,nyc,zc);
              _cimg_draw_fill_test_neighbor(xc-1,nyc,zc,xc!=0);
              _cimg_draw_fill_test_neighbor(xc+1,nyc,zc,xc<W1);
              _cimg_draw_fill_test_neighbor(xc,nyc,zc-1,zc!=0);
              _cimg_draw_fill_test_neighbor(xc,nyc,zc+1,zc<D1);
            }
          } while (cont);
          nyc = yc;
          do { // Y-forward
            if ((++nyc)<=H1) { _cimg_draw_fill_test(xc,nyc,zc,cont); } else cont = false;
            if (cont) {
              _cimg_draw_fill_set(xc,nyc,zc);
              _cimg_draw_fill_test_neighbor(xc-1,nyc,zc,xc!=0);
              _cimg_draw_fill_test_neighbor(xc+1,nyc,zc,xc<W1);
              _cimg_draw_fill_test_neighbor(xc,nyc,zc-1,zc!=0);
              _cimg_draw_fill_test_neighbor(xc,nyc,zc+1,zc<D1);
            }
          } while (cont);
          unsigned int nzc = zc;
          do { // Z-backward
            if (nzc) { --nzc; _cimg_draw_fill_test(xc,yc,nzc,cont); } else cont = false;
            if (cont) {
              _cimg_draw_fill_set(xc,yc,nzc);
              _cimg_draw_fill_test_neighbor(xc-1,yc,nzc,xc!=0);
              _cimg_draw_fill_test_neighbor(xc+1,yc,nzc,xc<W1);
              _cimg_draw_fill_test_neighbor(xc,yc-1,nzc,yc!=0);
              _cimg_draw_fill_test_neighbor(xc,yc+1,nzc,yc<H1);
            }
          } while (cont);
          nzc = zc;
          do { // Z-forward
            if ((++nzc)<=D1) { _cimg_draw_fill_test(xc,yc,nzc,cont); } else cont = false;
            if (cont) {
              _cimg_draw_fill_set(xc,nyc,zc);
              _cimg_draw_fill_test_neighbor(xc-1,yc,nzc,xc!=0);
              _cimg_draw_fill_test_neighbor(xc+1,yc,nzc,xc<W1);
              _cimg_draw_fill_test_neighbor(xc,yc-1,nzc,yc!=0);
              _cimg_draw_fill_test_neighbor(xc,yc+1,nzc,yc<H1);
            }
          } while (cont);
        } while (posr1>posr0);
        else do { // 2D version of the filling algorithm
          const unsigned int *pcurr = remaining.ptr(0,posr0++), xc = *(pcurr++), yc = *(pcurr++);
          if (posr0>=512) { remaining.translate(0,posr0); posr1-=posr0; posr0 = 0; }
          bool cont, res;
          unsigned int nxc = xc;
          do { // X-backward
            _cimg_draw_fill_set(nxc,yc,0);
            _cimg_draw_fill_test_neighbor(nxc,yc-1,0,yc!=0);
            _cimg_draw_fill_test_neighbor(nxc,yc+1,0,yc<H1);
            if (high_connexity) {
              _cimg_draw_fill_test_neighbor(nxc-1,yc-1,0,(nxc!=0 && yc!=0));
              _cimg_draw_fill_test_neighbor(nxc+1,yc-1,0,(nxc<W1 && yc!=0));
              _cimg_draw_fill_test_neighbor(nxc-1,yc+1,0,(nxc!=0 && yc<H1));
              _cimg_draw_fill_test_neighbor(nxc+1,yc+1,0,(nxc<W1 && yc<H1));
            }
            if (nxc) { --nxc; _cimg_draw_fill_test(nxc,yc,0,cont); } else cont = false;
          } while (cont);
          nxc = xc;
          do { // X-forward
            if ((++nxc)<=W1) { _cimg_draw_fill_test(nxc,yc,0,cont); } else cont = false;
            if (cont) {
              _cimg_draw_fill_set(nxc,yc,0);
              _cimg_draw_fill_test_neighbor(nxc,yc-1,0,yc!=0);
              _cimg_draw_fill_test_neighbor(nxc,yc+1,0,yc<H1);
              if (high_connexity) {
                _cimg_draw_fill_test_neighbor(nxc-1,yc-1,0,(nxc!=0 && yc!=0));
                _cimg_draw_fill_test_neighbor(nxc+1,yc-1,0,(nxc<W1 && yc!=0));
                _cimg_draw_fill_test_neighbor(nxc-1,yc+1,0,(nxc!=0 && yc<H1));
                _cimg_draw_fill_test_neighbor(nxc+1,yc+1,0,(nxc<W1 && yc<H1));
              }
            }
          } while (cont);
          unsigned int nyc = yc;
          do { // Y-backward
            if (nyc) { --nyc; _cimg_draw_fill_test(xc,nyc,0,cont); } else cont = false;
            if (cont) {
              _cimg_draw_fill_set(xc,nyc,0);
              _cimg_draw_fill_test_neighbor(xc-1,nyc,0,xc!=0);
              _cimg_draw_fill_test_neighbor(xc+1,nyc,0,xc<W1);
              if (high_connexity) {
                _cimg_draw_fill_test_neighbor(xc-1,nyc-1,0,(xc!=0 && nyc!=0));
                _cimg_draw_fill_test_neighbor(xc+1,nyc-1,0,(xc<W1 && nyc!=0));
                _cimg_draw_fill_test_neighbor(xc-1,nyc+1,0,(xc!=0 && nyc<H1));
                _cimg_draw_fill_test_neighbor(xc+1,nyc+1,0,(xc<W1 && nyc<H1));
              }
            }
          } while (cont);
          nyc = yc;
          do { // Y-forward
            if ((++nyc)<=H1) { _cimg_draw_fill_test(xc,nyc,0,cont); } else cont = false;
            if (cont) {
              _cimg_draw_fill_set(xc,nyc,0);
              _cimg_draw_fill_test_neighbor(xc-1,nyc,0,xc!=0);
              _cimg_draw_fill_test_neighbor(xc+1,nyc,0,xc<W1);
              if (high_connexity) {
                _cimg_draw_fill_test_neighbor(xc-1,nyc-1,0,(xc!=0 && nyc!=0));
                _cimg_draw_fill_test_neighbor(xc+1,nyc-1,0,(xc<W1 && nyc!=0));
                _cimg_draw_fill_test_neighbor(xc-1,nyc+1,0,(xc!=0 && nyc<H1));
                _cimg_draw_fill_test_neighbor(xc+1,nyc+1,0,(xc<W1 && nyc<H1));
              }
            }
          } while (cont);
        } while (posr1>posr0);
        if (noregion) cimg_for(region,ptr,t) if (*ptr==noregion) *ptr = (t)0;
      }
      return *this;
    }

    //! Draw a 3D filled region starting from a point (\c x,\c y,\ z) in the instance image.
    template<typename tc, typename t>
    CImg<T>& draw_fill(const int x, const int y, const int z,
                       const CImg<tc>& color, const float opacity,
                       CImg<t>& region, const float sigma=0, const bool high_connexity=false) {
      return draw_fill(x,y,z,color.data,opacity,region,sigma,high_connexity);
    }

    //! Draw a 3D filled region starting from a point (\c x,\c y,\ z) in the instance image.
    /**
       \param x = X-coordinate of the starting point of the region to fill.
       \param y = Y-coordinate of the starting point of the region to fill.
       \param z = Z-coordinate of the starting point of the region to fill.
       \param color = an array of dimv() values of type \c T, defining the drawing color.
       \param sigma = tolerance concerning neighborhood values.
       \param opacity = opacity of the drawing.
    **/
    template<typename tc>
    CImg<T>& draw_fill(const int x, const int y, const int z,
                       const tc *const color, const float opacity=1,
                       const float sigma=0, const bool high_connexity=false) {
      CImg<boolT> tmp;
      return draw_fill(x,y,z,color,opacity,tmp,sigma,high_connexity);
    }

    //! Draw a 3D filled region starting from a point (\c x,\c y,\ z) in the instance image.
    template<typename tc>
    CImg<T>& draw_fill(const int x, const int y, const int z,
                       const CImg<tc>& color, const float opacity=1,
                       const float sigma=0, const bool high_connexity=false) {
      return draw_fill(x,y,z,color.data,opacity,sigma,high_connexity);
    }

    //! Draw a 2D filled region starting from a point (\c x,\c y) in the instance image.
    /**
       \param x = X-coordinate of the starting point of the region to fill.
       \param y = Y-coordinate of the starting point of the region to fill.
       \param color = an array of dimv() values of type \c T, defining the drawing color.
       \param sigma = tolerance concerning neighborhood values.
       \param opacity = opacity of the drawing.
    **/
    template<typename tc>
    CImg<T>& draw_fill(const int x, const int y,
                       const tc *const color, const float opacity=1,
                       const float sigma=0, const bool high_connexity=false) {
      CImg<boolT> tmp;
      return draw_fill(x,y,0,color,opacity,tmp,sigma,high_connexity);
    }

    //! Draw a 2D filled region starting from a point (\c x,\c y) in the instance image.
    template<typename tc>
    CImg<T>& draw_fill(const int x, const int y,
                       const CImg<tc>& color, const float opacity=1,
                       const float sigma=0, const bool high_connexity=false) {
      return draw_fill(x,y,color.data,opacity,sigma,high_connexity);
    }

    //! Draw a plasma random texture.
    /**
       \param x0 = X-coordinate of the upper-left corner of the plasma.
       \param y0 = Y-coordinate of the upper-left corner of the plasma.
       \param x1 = X-coordinate of the lower-right corner of the plasma.
       \param y1 = Y-coordinate of the lower-right corner of the plasma.
       \param alpha = Alpha-parameter of the plasma.
       \param beta = Beta-parameter of the plasma.
       \param opacity = opacity of the drawing.
    **/
    CImg<T>& draw_plasma(const int x0, const int y0, const int x1, const int y1,
                         const float alpha=1, const float beta=1,
                         const float opacity=1) {
      if (!is_empty()) {
        const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
        int nx0 = x0, nx1 = x1, ny0 = y0, ny1 = y1;
        if (nx1<nx0) cimg::swap(nx0,nx1);
        if (ny1<ny0) cimg::swap(ny0,ny1);
        if (nx0<0) nx0 = 0;
        if (nx1>=dimx()) nx1 = width-1;
        if (ny0<0) ny0 = 0;
        if (ny1>=dimy()) ny1 = height-1;
        const int xc = (nx0+nx1)/2, yc = (ny0+ny1)/2, dx = (xc-nx0), dy = (yc-ny0);
        const Tfloat dc = (Tfloat)(cimg_std::sqrt((float)(dx*dx+dy*dy))*alpha + beta);
        Tfloat val = 0;
        cimg_forV(*this,k) {
          if (opacity>=1) {
            const Tfloat
              val0 = (Tfloat)((*this)(nx0,ny0,0,k)), val1 = (Tfloat)((*this)(nx1,ny0,0,k)),
              val2 = (Tfloat)((*this)(nx0,ny1,0,k)), val3 = (Tfloat)((*this)(nx1,ny1,0,k));
            (*this)(xc,ny0,0,k) = (T)((val0+val1)/2);
            (*this)(xc,ny1,0,k) = (T)((val2+val3)/2);
            (*this)(nx0,yc,0,k) = (T)((val0+val2)/2);
            (*this)(nx1,yc,0,k) = (T)((val1+val3)/2);
            do {
              val = (Tfloat)(0.25f*((Tfloat)((*this)(nx0,ny0,0,k)) +
                                   (Tfloat)((*this)(nx1,ny0,0,k)) +
                                   (Tfloat)((*this)(nx1,ny1,0,k)) +
                                   (Tfloat)((*this)(nx0,ny1,0,k))) +
                            dc*cimg::grand());
            } while (val<(Tfloat)cimg::type<T>::min() || val>(Tfloat)cimg::type<T>::max());
            (*this)(xc,yc,0,k) = (T)val;
          } else {
            const Tfloat
              val0 = (Tfloat)((*this)(nx0,ny0,0,k)), val1 = (Tfloat)((*this)(nx1,ny0,0,k)),
              val2 = (Tfloat)((*this)(nx0,ny1,0,k)), val3 = (Tfloat)((*this)(nx1,ny1,0,k));
            (*this)(xc,ny0,0,k) = (T)(((val0+val1)*nopacity + copacity*(*this)(xc,ny0,0,k))/2);
            (*this)(xc,ny1,0,k) = (T)(((val2+val3)*nopacity + copacity*(*this)(xc,ny1,0,k))/2);
            (*this)(nx0,yc,0,k) = (T)(((val0+val2)*nopacity + copacity*(*this)(nx0,yc,0,k))/2);
            (*this)(nx1,yc,0,k) = (T)(((val1+val3)*nopacity + copacity*(*this)(nx1,yc,0,k))/2);
            do {
              val = (Tfloat)(0.25f*(((Tfloat)((*this)(nx0,ny0,0,k)) +
                                    (Tfloat)((*this)(nx1,ny0,0,k)) +
                                    (Tfloat)((*this)(nx1,ny1,0,k)) +
                                    (Tfloat)((*this)(nx0,ny1,0,k))) +
                                   dc*cimg::grand())*nopacity + copacity*(*this)(xc,yc,0,k));
            } while (val<(Tfloat)cimg::type<T>::min() || val>(Tfloat)cimg::type<T>::max());
            (*this)(xc,yc,0,k) = (T)val;
          }
        }
        if (xc!=nx0 || yc!=ny0) {
          draw_plasma(nx0,ny0,xc,yc,alpha,beta,opacity);
          draw_plasma(xc,ny0,nx1,yc,alpha,beta,opacity);
          draw_plasma(nx0,yc,xc,ny1,alpha,beta,opacity);
          draw_plasma(xc,yc,nx1,ny1,alpha,beta,opacity);
        }
      }
      return *this;
    }

    //! Draw a plasma random texture.
    /**
       \param alpha = Alpha-parameter of the plasma.
       \param beta = Beta-parameter of the plasma.
       \param opacity = opacity of the drawing.
    **/
    CImg<T>& draw_plasma(const float alpha=1, const float beta=1,
                         const float opacity=1) {
      return draw_plasma(0,0,width-1,height-1,alpha,beta,opacity);
    }

    //! Draw a quadratic Mandelbrot or Julia fractal set, computed using the Escape Time Algorithm.
    template<typename tc>
    CImg<T>& draw_mandelbrot(const int x0, const int y0, const int x1, const int y1,
                             const CImg<tc>& color_palette, const float opacity=1,
                             const double z0r=-2, const double z0i=-2, const double z1r=2, const double z1i=2,
                             const unsigned int itermax=255,
                             const bool normalized_iteration=false,
                             const bool julia_set=false,
                             const double paramr=0, const double parami=0) {
      if (is_empty()) return *this;
      CImg<tc> palette;
      if (color_palette) palette.assign(color_palette.data,color_palette.size()/color_palette.dim,1,1,color_palette.dim,true);
      if (palette && palette.dim!=dim)
        throw CImgArgumentException("CImg<%s>::draw_mandelbrot() : Specified color palette (%u,%u,%u,%u,%p) is not \n"
                                    "compatible with instance image (%u,%u,%u,%u,%p).",
                                    pixel_type(),color_palette.width,color_palette.height,color_palette.depth,color_palette.dim,
                                    color_palette.data,width,height,depth,dim,data);
      const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0), ln2 = (float)cimg_std::log(2.0);
      unsigned int iter = 0;
      cimg_for_inXY(*this,x0,y0,x1,y1,p,q) {
        const double x = z0r + p*(z1r-z0r)/width, y = z0i + q*(z1i-z0i)/height;
        double zr, zi, cr, ci;
        if (julia_set) { zr = x; zi = y; cr = paramr; ci = parami; }
        else { zr = paramr; zi = parami; cr = x; ci = y; }
        for (iter=1; zr*zr + zi*zi<=4 && iter<=itermax; ++iter) {
          const double temp = zr*zr - zi*zi + cr;
          zi = 2*zr*zi + ci;
          zr = temp;
        }
        if (iter>itermax) {
          if (palette) {
            if (opacity>=1) cimg_forV(*this,k) (*this)(p,q,0,k) = (T)palette(0,k);
            else cimg_forV(*this,k) (*this)(p,q,0,k) = (T)(palette(0,k)*nopacity + (*this)(p,q,0,k)*copacity);
          } else {
            if (opacity>=1) cimg_forV(*this,k) (*this)(p,q,0,k) = (T)0;
            else cimg_forV(*this,k) (*this)(p,q,0,k) = (T)((*this)(p,q,0,k)*copacity);
          }
        } else if (normalized_iteration) {
          const float
            normz = (float)cimg::abs(zr*zr+zi*zi),
            niter = (float)(iter + 1 - cimg_std::log(cimg_std::log(normz))/ln2);
          if (palette) {
            if (opacity>=1) cimg_forV(*this,k) (*this)(p,q,0,k) = (T)palette._linear_atX(niter,k);
            else cimg_forV(*this,k) (*this)(p,q,0,k) = (T)(palette._linear_atX(niter,k)*nopacity + (*this)(p,q,0,k)*copacity);
          } else {
            if (opacity>=1) cimg_forV(*this,k) (*this)(p,q,0,k) = (T)niter;
            else cimg_forV(*this,k) (*this)(p,q,0,k) = (T)(niter*nopacity + (*this)(p,q,0,k)*copacity);
          }
        } else {
          if (palette) {
            if (opacity>=1) cimg_forV(*this,k) (*this)(p,q,0,k) = (T)palette._atX(iter,k);
            else cimg_forV(*this,k) (*this)(p,q,0,k) = (T)(palette(iter,k)*nopacity + (*this)(p,q,0,k)*copacity);
          } else {
            if (opacity>=1) cimg_forV(*this,k) (*this)(p,q,0,k) = (T)iter;
            else cimg_forV(*this,k) (*this)(p,q,0,k) = (T)(iter*nopacity + (*this)(p,q,0,k)*copacity);
          }
        }
      }
      return *this;
    }

    //! Draw a quadratic Mandelbrot or Julia fractal set, computed using the Escape Time Algorithm.
    template<typename tc>
    CImg<T>& draw_mandelbrot(const CImg<tc>& color_palette, const float opacity=1,
                             const double z0r=-2, const double z0i=-2, const double z1r=2, const double z1i=2,
                             const unsigned int itermax=255,
                             const bool normalized_iteration=false,
                             const bool julia_set=false,
                             const double paramr=0, const double parami=0) {
      return draw_mandelbrot(0,0,width-1,height-1,color_palette,opacity,z0r,z0i,z1r,z1i,itermax,normalized_iteration,julia_set,paramr,parami);
    }

    //! Draw a 1D gaussian function in the instance image.
    /**
       \param xc = X-coordinate of the gaussian center.
       \param sigma = Standard variation of the gaussian distribution.
       \param color = array of dimv() values of type \c T, defining the drawing color.
       \param opacity = opacity of the drawing.
    **/
    template<typename tc>
    CImg<T>& draw_gaussian(const float xc, const float sigma,
                           const tc *const color, const float opacity=1) {
      if (is_empty()) return *this;
      if (!color)
        throw CImgArgumentException("CImg<%s>::draw_gaussian() : Specified color is (null)",
                                    pixel_type());
      const float sigma2 = 2*sigma*sigma, nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
      const unsigned int whz = width*height*depth;
      const tc *col = color;
      cimg_forX(*this,x) {
        const float dx = (x - xc), val = (float)cimg_std::exp(-dx*dx/sigma2);
        T *ptrd = ptr(x,0,0,0);
        if (opacity>=1) cimg_forV(*this,k) { *ptrd = (T)(val*(*col++)); ptrd+=whz; }
        else cimg_forV(*this,k) { *ptrd = (T)(nopacity*val*(*col++) + *ptrd*copacity); ptrd+=whz; }
        col-=dim;
      }
      return *this;
    }

    //! Draw a 1D gaussian function in the instance image.
    template<typename tc>
    CImg<T>& draw_gaussian(const float xc, const float sigma,
                           const CImg<tc>& color, const float opacity=1) {
      return draw_gaussian(xc,sigma,color.data,opacity);
    }

    //! Draw an anisotropic 2D gaussian function.
    /**
       \param xc = X-coordinate of the gaussian center.
       \param yc = Y-coordinate of the gaussian center.
       \param tensor = 2x2 covariance matrix.
       \param color = array of dimv() values of type \c T, defining the drawing color.
       \param opacity = opacity of the drawing.
    **/
    template<typename t, typename tc>
    CImg<T>& draw_gaussian(const float xc, const float yc, const CImg<t>& tensor,
                           const tc *const color, const float opacity=1) {
      if (is_empty()) return *this;
      typedef typename cimg::superset<t,float>::type tfloat;
      if (tensor.width!=2 || tensor.height!=2 || tensor.depth!=1 || tensor.dim!=1)
        throw CImgArgumentException("CImg<%s>::draw_gaussian() : Tensor parameter (%u,%u,%u,%u,%p) is not a 2x2 matrix.",
                                    pixel_type(),tensor.width,tensor.height,tensor.depth,tensor.dim,tensor.data);
      if (!color)
        throw CImgArgumentException("CImg<%s>::draw_gaussian() : Specified color is (null)",
                                    pixel_type());
      const CImg<tfloat> invT = tensor.get_invert(), invT2 = (invT*invT)/(-2.0);
      const tfloat a = invT2(0,0), b = 2*invT2(1,0), c = invT2(1,1);
      const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
      const unsigned int whz = width*height*depth;
      const tc *col = color;
      float dy = -yc;
      cimg_forY(*this,y) {
        float dx = -xc;
        cimg_forX(*this,x) {
          const float val = (float)cimg_std::exp(a*dx*dx + b*dx*dy + c*dy*dy);
          T *ptrd = ptr(x,y,0,0);
          if (opacity>=1) cimg_forV(*this,k) { *ptrd = (T)(val*(*col++)); ptrd+=whz; }
          else cimg_forV(*this,k) { *ptrd = (T)(nopacity*val*(*col++) + *ptrd*copacity); ptrd+=whz; }
          col-=dim;
          ++dx;
        }
        ++dy;
      }
      return *this;
    }

    //! Draw an anisotropic 2D gaussian function.
    template<typename t, typename tc>
    CImg<T>& draw_gaussian(const float xc, const float yc, const CImg<t>& tensor,
                           const CImg<tc>& color, const float opacity=1) {
      return draw_gaussian(xc,yc,tensor,color.data,opacity);
    }

    //! Draw an anisotropic 2D gaussian function.
    template<typename tc>
    CImg<T>& draw_gaussian(const int xc, const int yc, const float r1, const float r2, const float ru, const float rv,
                           const tc *const color, const float opacity=1) {
      const double
        a = r1*ru*ru + r2*rv*rv,
        b = (r1-r2)*ru*rv,
        c = r1*rv*rv + r2*ru*ru;
      const CImg<Tfloat> tensor(2,2,1,1, a,b,b,c);
      return draw_gaussian(xc,yc,tensor,color,opacity);
    }

    //! Draw an anisotropic 2D gaussian function.
    template<typename tc>
    CImg<T>& draw_gaussian(const int xc, const int yc, const float r1, const float r2, const float ru, const float rv,
                           const CImg<tc>& color, const float opacity=1) {
      return draw_gaussian(xc,yc,r1,r2,ru,rv,color.data,opacity);
    }

    //! Draw an isotropic 2D gaussian function.
    /**
       \param xc = X-coordinate of the gaussian center.
       \param yc = Y-coordinate of the gaussian center.
       \param sigma = standard variation of the gaussian distribution.
       \param color = array of dimv() values of type \c T, defining the drawing color.
       \param opacity = opacity of the drawing.
    **/
    template<typename tc>
    CImg<T>& draw_gaussian(const float xc, const float yc, const float sigma,
                           const tc *const color, const float opacity=1) {
      return draw_gaussian(xc,yc,CImg<floatT>::diagonal(sigma,sigma),color,opacity);
    }

    //! Draw an isotropic 2D gaussian function.
    template<typename tc>
    CImg<T>& draw_gaussian(const float xc, const float yc, const float sigma,
                           const CImg<tc>& color, const float opacity=1) {
      return draw_gaussian(xc,yc,sigma,color.data,opacity);
    }

    //! Draw an anisotropic 3D gaussian function.
    /**
       \param xc = X-coordinate of the gaussian center.
       \param yc = Y-coordinate of the gaussian center.
       \param zc = Z-coordinate of the gaussian center.
       \param tensor = 3x3 covariance matrix.
       \param color = array of dimv() values of type \c T, defining the drawing color.
       \param opacity = opacity of the drawing.
    **/
    template<typename t, typename tc>
    CImg<T>& draw_gaussian(const float xc, const float yc, const float zc, const CImg<t>& tensor,
                           const tc *const color, const float opacity=1) {
      if (is_empty()) return *this;
      typedef typename cimg::superset<t,float>::type tfloat;
      if (tensor.width!=3 || tensor.height!=3 || tensor.depth!=1 || tensor.dim!=1)
        throw CImgArgumentException("CImg<%s>::draw_gaussian() : Tensor parameter (%u,%u,%u,%u,%p) is not a 3x3 matrix.",
                                    pixel_type(),tensor.width,tensor.height,tensor.depth,tensor.dim,tensor.data);
      const CImg<tfloat> invT = tensor.get_invert(), invT2 = (invT*invT)/(-2.0);
      const tfloat a = invT(0,0), b = 2*invT(1,0), c = 2*invT(2,0), d = invT(1,1), e = 2*invT(2,1), f = invT(2,2);
      const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
      const unsigned int whz = width*height*depth;
      const tc *col = color;
      cimg_forXYZ(*this,x,y,z) {
        const float
          dx = (x - xc), dy = (y - yc), dz = (z - zc),
          val = (float)cimg_std::exp(a*dx*dx + b*dx*dy + c*dx*dz + d*dy*dy + e*dy*dz + f*dz*dz);
        T *ptrd = ptr(x,y,z,0);
        if (opacity>=1) cimg_forV(*this,k) { *ptrd = (T)(val*(*col++)); ptrd+=whz; }
        else cimg_forV(*this,k) { *ptrd = (T)(nopacity*val*(*col++) + *ptrd*copacity); ptrd+=whz; }
        col-=dim;
      }
      return *this;
    }

    //! Draw an anisotropic 3D gaussian function.
    template<typename t, typename tc>
    CImg<T>& draw_gaussian(const float xc, const float yc, const float zc, const CImg<t>& tensor,
                           const CImg<tc>& color, const float opacity=1) {
      return draw_gaussian(xc,yc,zc,tensor,color.data,opacity);
    }

    //! Draw an isotropic 3D gaussian function.
   /**
       \param xc = X-coordinate of the gaussian center.
       \param yc = Y-coordinate of the gaussian center.
       \param zc = Z-coordinate of the gaussian center.
       \param sigma = standard variation of the gaussian distribution.
       \param color = array of dimv() values of type \c T, defining the drawing color.
       \param opacity = opacity of the drawing.
    **/
    template<typename tc>
    CImg<T>& draw_gaussian(const float xc, const float yc, const float zc, const float sigma,
                           const tc *const color, const float opacity=1) {
      return draw_gaussian(xc,yc,zc,CImg<floatT>::diagonal(sigma,sigma,sigma),color,opacity);
    }

    //! Draw an isotropic 3D gaussian function.
    template<typename tc>
    CImg<T>& draw_gaussian(const float xc, const float yc, const float zc, const float sigma,
                           const CImg<tc>& color, const float opacity=1) {
      return draw_gaussian(xc,yc,zc,sigma,color.data,opacity);
    }

    // Draw a 3D object (internal)
    template<typename tc, typename to>
    void _draw_object3d_sprite(const int x, const int y,
                               const CImg<tc>& color, const CImg<to>& opacity, const CImg<T>& sprite) {
      if (opacity.width==color.width && opacity.height==color.height)
        draw_image(x,y,sprite,opacity.get_resize(sprite.width,sprite.height,1,sprite.dim,1));
      else
        draw_image(x,y,sprite,opacity(0));
    }

    template<typename tc>
    void _draw_object3d_sprite(const int x, const int y,
                               const CImg<tc>& color, const float opacity, const CImg<T>& sprite) {
      if (color) draw_image(x,y,sprite,opacity);
    }

    template<typename tp, typename tf, typename tc, typename to>
    CImg<T>& _draw_object3d(void *const pboard, float *const zbuffer,
                            const float X, const float Y, const float Z,
                            const tp& points, const unsigned int nb_points,
                            const CImgList<tf>& primitives,
                            const CImgList<tc>& colors,
                            const to& opacities, const unsigned int nb_opacities,
                            const unsigned int render_type,
                            const bool double_sided, const float focale,
                            const float lightx, const float lighty, const float lightz,
                            const float specular_light, const float specular_shine) {
      if (is_empty()) return *this;
#ifndef cimg_use_board
      if (pboard) return *this;
#endif
      const float
        nspec = 1-(specular_light<0?0:(specular_light>1?1:specular_light)),
        nspec2 = 1+(specular_shine<0?0:specular_shine),
        nsl1 = (nspec2-1)/cimg::sqr(nspec-1),
        nsl2 = (1-2*nsl1*nspec),
        nsl3 = nspec2-nsl1-nsl2;

      // Create light texture for phong-like rendering
      static CImg<floatT> light_texture;
      if (render_type==5) {
        if (colors.size>primitives.size) light_texture.assign(colors[primitives.size])/=255;
        else {
          static float olightx = 0, olighty = 0, olightz = 0, ospecular_shine = 0;
          if (!light_texture || lightx!=olightx || lighty!=olighty || lightz!=olightz || specular_shine!=ospecular_shine) {
            light_texture.assign(512,512);
            const float white[] = { 1 },
              dlx = lightx-X, dly = lighty-Y, dlz = lightz-Z,
                nl = (float)cimg_std::sqrt(dlx*dlx+dly*dly+dlz*dlz),
                nlx = light_texture.width/2*(1+dlx/nl),
                nly = light_texture.height/2*(1+dly/nl);
              light_texture.draw_gaussian(nlx,nly,light_texture.width/3.0f,white);
              cimg_forXY(light_texture,x,y) {
                const float factor = light_texture(x,y);
                if (factor>nspec) light_texture(x,y) = cimg::min(2,nsl1*factor*factor+nsl2*factor+nsl3);
              }
              olightx = lightx; olighty = lighty; olightz = lightz; ospecular_shine = specular_shine;
          }
        }
      }

      // Compute 3D to 2D projection
      CImg<floatT> projections(nb_points,2);
      cimg_forX(projections,l) {
        const float
          x = (float)points(l,0),
          y = (float)points(l,1),
          z = (float)points(l,2);
        const float projectedz = z + Z + focale;
        projections(l,1) = Y + focale*y/projectedz;
        projections(l,0) = X + focale*x/projectedz;
      }

      // Compute and sort visible primitives
      CImg<uintT> visibles(primitives.size);
      CImg<floatT> zrange(primitives.size);
      unsigned int nb_visibles = 0;
      const float zmin = -focale+1.5f;
      { cimglist_for(primitives,l) {
        const CImg<tf>& primitive = primitives[l];
        switch (primitive.size()) {

        case 1 : { // Point
          const unsigned int i0 = (unsigned int)primitive(0);
          const float x0 = projections(i0,0), y0 = projections(i0,1), z0 = (float)(Z+points(i0,2));
          if (z0>zmin && x0>=0 && x0<width && y0>=0 && y0<height) {
            visibles(nb_visibles) = (unsigned int)l;
            zrange(nb_visibles++) = z0;
          }
        } break;
        case 5 : { // Sphere
          const unsigned int
            i0 = (unsigned int)primitive(0),
            i1 = (unsigned int)primitive(1),
            i2 = (unsigned int)primitive(2);
          const float x0 = projections(i0,0), y0 = projections(i0,1), z0 = (float)(Z+points(i0,2));
          int radius;
          if (i2) radius = (int)(i2*focale/(z0+focale));
          else {
            const float x1 = projections(i1,0), y1 = projections(i1,1);
            const int deltax = (int)(x1-x0), deltay = (int)(y1-y0);
            radius = (int)cimg_std::sqrt((float)(deltax*deltax + deltay*deltay));
          }
          if (z0>zmin && x0+radius>=0 && x0-radius<width && y0+radius>=0 && y0-radius<height) {
            visibles(nb_visibles) = (unsigned int)l;
            zrange(nb_visibles++) = z0;
          }
        } break;
        case 2 : // Line
        case 6 : {
          const unsigned int
            i0 = (unsigned int)primitive(0),
            i1 = (unsigned int)primitive(1);
          const float
            x0 = projections(i0,0), y0 = projections(i0,1), z0 = (float)(Z+points(i0,2)),
            x1 = projections(i1,0), y1 = projections(i1,1), z1 = (float)(Z+points(i1,2));
          float xm, xM, ym, yM;
          if (x0<x1) { xm = x0; xM = x1; } else { xm = x1; xM = x0; }
          if (y0<y1) { ym = y0; yM = y1; } else { ym = y1; yM = y0; }
          if (z0>zmin && z1>zmin && xM>=0 && xm<width && yM>=0 && ym<height) {
            visibles(nb_visibles) = (unsigned int)l;
            zrange(nb_visibles++) = 0.5f*(z0+z1);
          }
        } break;
        case 3 :  // Triangle
        case 9 : {
          const unsigned int
            i0 = (unsigned int)primitive(0),
            i1 = (unsigned int)primitive(1),
            i2 = (unsigned int)primitive(2);
          const float
            x0 = projections(i0,0), y0 = projections(i0,1), z0 = (float)(Z+points(i0,2)),
            x1 = projections(i1,0), y1 = projections(i1,1), z1 = (float)(Z+points(i1,2)),
            x2 = projections(i2,0), y2 = projections(i2,1), z2 = (float)(Z+points(i2,2));
          float xm, xM, ym, yM;
          if (x0<x1) { xm = x0; xM = x1; } else { xm = x1; xM = x0; }
          if (x2<xm) xm = x2;
          if (x2>xM) xM = x2;
          if (y0<y1) { ym = y0; yM = y1; } else { ym = y1; yM = y0; }
          if (y2<ym) ym = y2;
          if (y2>yM) yM = y2;
          if (z0>zmin && z1>zmin && z2>zmin && xM>=0 && xm<width && yM>=0 && ym<height) {
            const float d = (x1-x0)*(y2-y0)-(x2-x0)*(y1-y0);
            if (double_sided || d<0) {
              visibles(nb_visibles) = (unsigned int)l;
              zrange(nb_visibles++) = (z0+z1+z2)/3;
            }
          }
        } break;
        case 4 : // Rectangle
        case 12 : {
          const unsigned int
            i0 = (unsigned int)primitive(0),
            i1 = (unsigned int)primitive(1),
            i2 = (unsigned int)primitive(2),
            i3 = (unsigned int)primitive(3);
          const float
            x0 = projections(i0,0), y0 = projections(i0,1), z0 = (float)(Z+points(i0,2)),
            x1 = projections(i1,0), y1 = projections(i1,1), z1 = (float)(Z+points(i1,2)),
            x2 = projections(i2,0), y2 = projections(i2,1), z2 = (float)(Z+points(i2,2)),
            x3 = projections(i3,0), y3 = projections(i3,1), z3 = (float)(Z+points(i3,2));
          float xm, xM, ym, yM;
          if (x0<x1) { xm = x0; xM = x1; } else { xm = x1; xM = x0; }
          if (x2<xm) xm = x2;
          if (x2>xM) xM = x2;
          if (x3<xm) xm = x3;
          if (x3>xM) xM = x3;
          if (y0<y1) { ym = y0; yM = y1; } else { ym = y1; yM = y0; }
          if (y2<ym) ym = y2;
          if (y2>yM) yM = y2;
          if (y3<ym) ym = y3;
          if (y3>yM) yM = y3;
          if (z0>zmin && z1>zmin && z2>zmin && z3>zmin && xM>=0 && xm<width && yM>=0 && ym<height) {
            const float d = (x1 - x0)*(y2 - y0) - (x2 - x0)*(y1 - y0);
            if (double_sided || d<0) {
              visibles(nb_visibles) = (unsigned int)l;
              zrange(nb_visibles++) = (z0 + z1 + z2 + z3)/4;
            }
          }
        } break;
        default :
          throw CImgArgumentException("CImg<%s>::draw_object3d() : Primitive %u is invalid (size = %u, can be 1,2,3,4,5,6,9 or 12)",
                                      pixel_type(),l,primitive.size());
        }}
      }
      if (nb_visibles<=0) return *this;
      CImg<uintT> permutations;
      CImg<floatT>(zrange.data,nb_visibles,1,1,1,true).sort(permutations,false);

      // Compute light properties
      CImg<floatT> lightprops;
      switch (render_type) {
      case 3 : { // Flat Shading
        lightprops.assign(nb_visibles);
        cimg_forX(lightprops,l) {
          const CImg<tf>& primitive = primitives(visibles(permutations(l)));
          const unsigned int psize = primitive.size();
          if (psize==3 || psize==4 || psize==9 || psize==12) {
            const unsigned int
              i0 = (unsigned int)primitive(0),
              i1 = (unsigned int)primitive(1),
              i2 = (unsigned int)primitive(2);
            const float
              x0 = (float)points(i0,0), y0 = (float)points(i0,1), z0 = (float)points(i0,2),
              x1 = (float)points(i1,0), y1 = (float)points(i1,1), z1 = (float)points(i1,2),
              x2 = (float)points(i2,0), y2 = (float)points(i2,1), z2 = (float)points(i2,2),
              dx1 = x1 - x0, dy1 = y1 - y0, dz1 = z1 - z0,
              dx2 = x2 - x0, dy2 = y2 - y0, dz2 = z2 - z0,
              nx = dy1*dz2 - dz1*dy2,
              ny = dz1*dx2 - dx1*dz2,
              nz = dx1*dy2 - dy1*dx2,
              norm = (float)cimg_std::sqrt(1e-5f + nx*nx + ny*ny + nz*nz),
              lx = X + (x0 + x1 + x2)/3 - lightx,
              ly = Y + (y0 + y1 + y2)/3 - lighty,
              lz = Z + (z0 + z1 + z2)/3 - lightz,
              nl = (float)cimg_std::sqrt(1e-5f + lx*lx + ly*ly + lz*lz),
              factor = cimg::max(cimg::abs(-lx*nx-ly*ny-lz*nz)/(norm*nl),0);
            lightprops[l] = factor<=nspec?factor:(nsl1*factor*factor + nsl2*factor + nsl3);
          } else lightprops[l] = 1;
        }
      } break;

      case 4 : // Gouraud Shading
      case 5 : { // Phong-Shading
        CImg<floatT> points_normals(nb_points,3,1,1,0);
        for (unsigned int l=0; l<nb_visibles; ++l) {
          const CImg<tf>& primitive = primitives[visibles(l)];
          const unsigned int psize = primitive.size();
          const bool
            triangle_flag = (psize==3) || (psize==9),
            rectangle_flag = (psize==4) || (psize==12);
          if (triangle_flag || rectangle_flag) {
            const unsigned int
              i0 = (unsigned int)primitive(0),
              i1 = (unsigned int)primitive(1),
              i2 = (unsigned int)primitive(2),
              i3 = rectangle_flag?(unsigned int)primitive(3):0;
            const float
              x0 = (float)points(i0,0), y0 = (float)points(i0,1), z0 = (float)points(i0,2),
              x1 = (float)points(i1,0), y1 = (float)points(i1,1), z1 = (float)points(i1,2),
              x2 = (float)points(i2,0), y2 = (float)points(i2,1), z2 = (float)points(i2,2),
              dx1 = x1 - x0, dy1 = y1 - y0, dz1 = z1 - z0,
              dx2 = x2 - x0, dy2 = y2 - y0, dz2 = z2 - z0,
              nnx = dy1*dz2 - dz1*dy2,
              nny = dz1*dx2 - dx1*dz2,
              nnz = dx1*dy2 - dy1*dx2,
              norm = 1e-5f + (float)cimg_std::sqrt(nnx*nnx + nny*nny + nnz*nnz),
              nx = nnx/norm,
              ny = nny/norm,
              nz = nnz/norm;
            points_normals(i0,0)+=nx; points_normals(i0,1)+=ny; points_normals(i0,2)+=nz;
            points_normals(i1,0)+=nx; points_normals(i1,1)+=ny; points_normals(i1,2)+=nz;
            points_normals(i2,0)+=nx; points_normals(i2,1)+=ny; points_normals(i2,2)+=nz;
            if (rectangle_flag) { points_normals(i3,0)+=nx; points_normals(i3,1)+=ny; points_normals(i3,2)+=nz; }
          }
        }

        if (double_sided) cimg_forX(points_normals,p) if (points_normals(p,2)>0) {
          points_normals(p,0) = -points_normals(p,0);
          points_normals(p,1) = -points_normals(p,1);
          points_normals(p,2) = -points_normals(p,2);
        }

        if (render_type==4) {
          lightprops.assign(nb_points);
          cimg_forX(lightprops,ll) {
            const float
              nx = points_normals(ll,0),
              ny = points_normals(ll,1),
              nz = points_normals(ll,2),
              norm = (float)cimg_std::sqrt(1e-5f + nx*nx + ny*ny + nz*nz),
              lx = (float)(X + points(ll,0) - lightx),
              ly = (float)(Y + points(ll,1) - lighty),
              lz = (float)(Z + points(ll,2) - lightz),
              nl = (float)cimg_std::sqrt(1e-5f + lx*lx + ly*ly + lz*lz),
              factor = cimg::max((-lx*nx-ly*ny-lz*nz)/(norm*nl),0);
            lightprops[ll] = factor<=nspec?factor:(nsl1*factor*factor + nsl2*factor + nsl3);
          }
        } else {
          const unsigned int
            lw2 = light_texture.width/2 - 1,
            lh2 = light_texture.height/2 - 1;
          lightprops.assign(nb_points,2);
          cimg_forX(lightprops,ll) {
            const float
              nx = points_normals(ll,0),
              ny = points_normals(ll,1),
              nz = points_normals(ll,2),
              norm = (float)cimg_std::sqrt(1e-5f + nx*nx + ny*ny + nz*nz),
              nnx = nx/norm,
              nny = ny/norm;
            lightprops(ll,0) = lw2*(1 + nnx);
            lightprops(ll,1) = lh2*(1 + nny);
          }
        }
      } break;
      }

      // Draw visible primitives
      const CImg<tc> default_color(1,dim,1,1,(tc)200);
      { for (unsigned int l = 0; l<nb_visibles; ++l) {
        const unsigned int n_primitive = visibles(permutations(l));
        const CImg<tf>& primitive = primitives[n_primitive];
        const CImg<tc>& color = n_primitive<colors.size?colors[n_primitive]:default_color;
        const float opac = n_primitive<nb_opacities?opacities(n_primitive,0):1.0f;
#ifdef cimg_use_board
        BoardLib::Board &board = *(BoardLib::Board*)pboard;
#endif

        switch (primitive.size()) {
        case 1 : { // Colored point or sprite
          const unsigned int n0 = (unsigned int)primitive[0];
          const int x0 = (int)projections(n0,0), y0 = (int)projections(n0,1);
          if (color.size()==dim) {
            draw_point(x0,y0,color,opac);
#ifdef cimg_use_board
            if (pboard) {
              board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opac*255));
              board.fillCircle((float)x0,dimy()-(float)y0,0);
            }
#endif
          } else {
            const float z = Z + points(n0,2);
            const int
              factor = (int)(focale*100/(z+focale)),
              sw = color.width*factor/200,
              sh = color.height*factor/200;
            if (x0+sw>=0 && x0-sw<dimx() && y0+sh>=0 && y0-sh<dimy()) {
              const CImg<T> sprite = color.get_resize(-factor,-factor,1,-100,render_type<=3?1:3);
              _draw_object3d_sprite(x0-sw,y0-sh,color,opacities[n_primitive%nb_opacities],sprite);
#ifdef cimg_use_board
                if (pboard) {
                  board.setPenColorRGBi(128,128,128);
                  board.setFillColor(BoardLib::Color::none);
                  board.drawRectangle((float)x0-sw,dimy()-(float)y0+sh,sw,sh);
                }
#endif
            }
          }
        } break;
        case 2 : { // Colored line
          const unsigned int
            n0 = (unsigned int)primitive[0],
            n1 = (unsigned int)primitive[1];
          const int
            x0 = (int)projections(n0,0), y0 = (int)projections(n0,1),
            x1 = (int)projections(n1,0), y1 = (int)projections(n1,1);
          const float
            z0 = points(n0,2) + Z + focale,
            z1 = points(n1,2) + Z + focale;
          if (render_type) {
            if (zbuffer) draw_line(zbuffer,x0,y0,z0,x1,y1,z1,color,opac);
            else draw_line(x0,y0,x1,y1,color,opac);
#ifdef cimg_use_board
            if (pboard) {
              board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opac*255));
              board.drawLine((float)x0,dimy()-(float)y0,x1,dimy()-(float)y1);
            }
#endif
          } else {
            draw_point(x0,y0,color,opac).draw_point(x1,y1,color,opac);
#ifdef cimg_use_board
            if (pboard) {
              board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opac*255));
              board.drawCircle((float)x0,dimy()-(float)y0,0);
              board.drawCircle((float)x1,dimy()-(float)y1,0);
            }
#endif
          }
        } break;
        case 5 : { // Colored sphere
          const unsigned int
            n0 = (unsigned int)primitive[0],
            n1 = (unsigned int)primitive[1],
            n2 = (unsigned int)primitive[2];
          const int
            x0 = (int)projections(n0,0), y0 = (int)projections(n0,1);
          int radius;
          if (n2) radius = (int)(n2*focale/(Z+points(n0,2)+focale));
          else {
            const int
              x1 = (int)projections(n1,0), y1 = (int)projections(n1,1),
              deltax = x1-x0, deltay = y1-y0;
            radius = (int)cimg_std::sqrt((float)(deltax*deltax + deltay*deltay));
          }
          switch (render_type) {
          case 0 :
            draw_point(x0,y0,color,opac);
#ifdef cimg_use_board
            if (pboard) {
              board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opac*255));
              board.fillCircle((float)x0,dimy()-(float)y0,0);
            }
#endif
            break;
          case 1 :
            draw_circle(x0,y0,radius,color,opac,~0U);
#ifdef cimg_use_board
            if (pboard) {
              board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opac*255));
              board.setFillColor(BoardLib::Color::none);
              board.drawCircle((float)x0,dimy()-(float)y0,(float)radius);
            }
#endif
            break;
          default :
            draw_circle(x0,y0,radius,color,opac);
#ifdef cimg_use_board
            if (pboard) {
              board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opac*255));
              board.fillCircle((float)x0,dimy()-(float)y0,(float)radius);
            }
#endif
            break;
          }
        } break;
        case 6 : { // Textured line
          const unsigned int
            n0 = (unsigned int)primitive[0],
            n1 = (unsigned int)primitive[1],
            tx0 = (unsigned int)primitive[2],
            ty0 = (unsigned int)primitive[3],
            tx1 = (unsigned int)primitive[4],
            ty1 = (unsigned int)primitive[5];
          const int
            x0 = (int)projections(n0,0), y0 = (int)projections(n0,1),
            x1 = (int)projections(n1,0), y1 = (int)projections(n1,1);
          const float
            z0 = points(n0,2) + Z + focale,
            z1 = points(n1,2) + Z + focale;
          if (render_type) {
            if (zbuffer) draw_line(zbuffer,x0,y0,z0,x1,y1,z1,color,tx0,ty0,tx1,ty1,opac);
            else draw_line(x0,y0,x1,y1,color,tx0,ty0,tx1,ty1,opac);
#ifdef cimg_use_board
            if (pboard) {
              board.setPenColorRGBi(128,128,128,(unsigned char)(opac*255));
              board.drawLine((float)x0,dimy()-(float)y0,(float)x1,dimy()-(float)y1);
            }
#endif
          } else {
            draw_point(x0,y0,color.get_vector_at(tx0,ty0),opac).
              draw_point(x1,y1,color.get_vector_at(tx1,ty1),opac);
#ifdef cimg_use_board
            if (pboard) {
              board.setPenColorRGBi(128,128,128,(unsigned char)(opac*255));
              board.drawCircle((float)x0,dimy()-(float)y0,0);
              board.drawCircle((float)x1,dimy()-(float)y1,0);
            }
#endif
          }
        } break;
        case 3 : { // Colored triangle
          const unsigned int
            n0 = (unsigned int)primitive[0],
            n1 = (unsigned int)primitive[1],
            n2 = (unsigned int)primitive[2];
          const int
            x0 = (int)projections(n0,0), y0 = (int)projections(n0,1),
            x1 = (int)projections(n1,0), y1 = (int)projections(n1,1),
            x2 = (int)projections(n2,0), y2 = (int)projections(n2,1);
          const float
            z0 = points(n0,2) + Z + focale,
            z1 = points(n1,2) + Z + focale,
            z2 = points(n2,2) + Z + focale;
          switch (render_type) {
          case 0 :
            draw_point(x0,y0,color,opac).draw_point(x1,y1,color,opac).draw_point(x2,y2,color,opac);
#ifdef cimg_use_board
            if (pboard) {
              board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opac*255));
              board.drawCircle((float)x0,dimy()-(float)y0,0);
              board.drawCircle((float)x1,dimy()-(float)y1,0);
              board.drawCircle((float)x2,dimy()-(float)y2,0);
            }
#endif
            break;
          case 1 :
            if (zbuffer)
              draw_line(zbuffer,x0,y0,z0,x1,y1,z1,color,opac).draw_line(zbuffer,x0,y0,z0,x2,y2,z2,color,opac).
                draw_line(zbuffer,x1,y1,z1,x2,y2,z2,color,opac);
            else
              draw_line(x0,y0,x1,y1,color,opac).draw_line(x0,y0,x2,y2,color,opac).
                draw_line(x1,y1,x2,y2,color,opac);
#ifdef cimg_use_board
            if (pboard) {
              board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opac*255));
              board.drawLine((float)x0,dimy()-(float)y0,(float)x1,dimy()-(float)y1);
              board.drawLine((float)x0,dimy()-(float)y0,(float)x2,dimy()-(float)y2);
              board.drawLine((float)x1,dimy()-(float)y1,(float)x2,dimy()-(float)y2);
            }
#endif
            break;
          case 2 :
            if (zbuffer) draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color,opac);
            else draw_triangle(x0,y0,x1,y1,x2,y2,color,opac);
#ifdef cimg_use_board
            if (pboard) {
              board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opac*255));
              board.fillTriangle((float)x0,dimy()-(float)y0,(float)x1,dimy()-(float)y1,(float)x2,dimy()-(float)y2);
            }
#endif
            break;
          case 3 :
            if (zbuffer) draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color.data,opac,lightprops(l));
            else _draw_triangle(x0,y0,x1,y1,x2,y2,color.data,opac,lightprops(l));
#ifdef cimg_use_board
            if (pboard) {
              const float lp = cimg::min(lightprops(l),1);
              board.setPenColorRGBi((unsigned char)(color[0]*lp),
                                     (unsigned char)(color[1]*lp),
                                     (unsigned char)(color[2]*lp),
                                     (unsigned char)(opac*255));
              board.fillTriangle((float)x0,dimy()-(float)y0,(float)x1,dimy()-(float)y1,(float)x2,dimy()-(float)y2);
            }
#endif
            break;
          case 4 :
            if (zbuffer) draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color,lightprops(n0),lightprops(n1),lightprops(n2),opac);
            else draw_triangle(x0,y0,x1,y1,x2,y2,color,lightprops(n0),lightprops(n1),lightprops(n2),opac);
#ifdef cimg_use_board
            if (pboard) {
              board.setPenColorRGBi((unsigned char)(color[0]),
                                     (unsigned char)(color[1]),
                                     (unsigned char)(color[2]),
                                     (unsigned char)(opac*255));
              board.fillGouraudTriangle((float)x0,dimy()-(float)y0,lightprops(n0),
                                         (float)x1,dimy()-(float)y1,lightprops(n1),
                                         (float)x2,dimy()-(float)y2,lightprops(n2));
            }
#endif
            break;
          case 5 : {
            const unsigned int
              lx0 = (unsigned int)lightprops(n0,0), ly0 = (unsigned int)lightprops(n0,1),
              lx1 = (unsigned int)lightprops(n1,0), ly1 = (unsigned int)lightprops(n1,1),
              lx2 = (unsigned int)lightprops(n2,0), ly2 = (unsigned int)lightprops(n2,1);
            if (zbuffer) draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color,light_texture,lx0,ly0,lx1,ly1,lx2,ly2,opac);
            else draw_triangle(x0,y0,x1,y1,x2,y2,color,light_texture,lx0,ly0,lx1,ly1,lx2,ly2,opac);
#ifdef cimg_use_board
            if (pboard) {
              const float
                l0 = light_texture((int)(light_texture.dimx()/2*(1+lightprops(n0,0))), (int)(light_texture.dimy()/2*(1+lightprops(n0,1)))),
                l1 = light_texture((int)(light_texture.dimx()/2*(1+lightprops(n1,0))), (int)(light_texture.dimy()/2*(1+lightprops(n1,1)))),
                l2 = light_texture((int)(light_texture.dimx()/2*(1+lightprops(n2,0))), (int)(light_texture.dimy()/2*(1+lightprops(n2,1))));
              board.setPenColorRGBi((unsigned char)(color[0]),
                                     (unsigned char)(color[1]),
                                     (unsigned char)(color[2]),
                                     (unsigned char)(opac*255));
              board.fillGouraudTriangle((float)x0,dimy()-(float)y0,l0,
                                         (float)x1,dimy()-(float)y1,l1,
                                         (float)x2,dimy()-(float)y2,l2);
            }
#endif
          } break;
          }
        } break;
        case 4 : { // Colored rectangle
          const unsigned int
            n0 = (unsigned int)primitive[0],
            n1 = (unsigned int)primitive[1],
            n2 = (unsigned int)primitive[2],
            n3 = (unsigned int)primitive[3];
          const int
            x0 = (int)projections(n0,0), y0 = (int)projections(n0,1),
            x1 = (int)projections(n1,0), y1 = (int)projections(n1,1),
            x2 = (int)projections(n2,0), y2 = (int)projections(n2,1),
            x3 = (int)projections(n3,0), y3 = (int)projections(n3,1);
          const float
            z0 = points(n0,2) + Z + focale,
            z1 = points(n1,2) + Z + focale,
            z2 = points(n2,2) + Z + focale,
            z3 = points(n3,2) + Z + focale;
          switch (render_type) {
          case 0 :
            draw_point(x0,y0,color,opac).draw_point(x1,y1,color,opac).
              draw_point(x2,y2,color,opac).draw_point(x3,y3,color,opac);
#ifdef cimg_use_board
            if (pboard) {
              board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opac*255));
              board.drawCircle((float)x0,dimy()-(float)y0,0);
              board.drawCircle((float)x1,dimy()-(float)y1,0);
              board.drawCircle((float)x2,dimy()-(float)y2,0);
              board.drawCircle((float)x3,dimy()-(float)y3,0);
            }
#endif
            break;
          case 1 :
            if (zbuffer)
              draw_line(zbuffer,x0,y0,z0,x1,y1,z1,color,opac).draw_line(zbuffer,x1,y1,z1,x2,y2,z2,color,opac).
                draw_line(zbuffer,x2,y2,z2,x3,y3,z3,color,opac).draw_line(zbuffer,x3,y3,z3,x0,y0,z0,color,opac);
            else
              draw_line(x0,y0,x1,y1,color,opac).draw_line(x1,y1,x2,y2,color,opac).
                draw_line(x2,y2,x3,y3,color,opac).draw_line(x3,y3,x0,y0,color,opac);
#ifdef cimg_use_board
            if (pboard) {
              board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opac*255));
              board.drawLine((float)x0,dimy()-(float)y0,(float)x1,dimy()-(float)y1);
              board.drawLine((float)x1,dimy()-(float)y1,(float)x2,dimy()-(float)y2);
              board.drawLine((float)x2,dimy()-(float)y2,(float)x3,dimy()-(float)y3);
              board.drawLine((float)x3,dimy()-(float)y3,(float)x0,dimy()-(float)y0);
            }
#endif
            break;
          case 2 :
            if (zbuffer)
              draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color,opac).draw_triangle(zbuffer,x0,y0,z0,x2,y2,z2,x3,y3,z3,color,opac);
            else
              draw_triangle(x0,y0,x1,y1,x2,y2,color,opac).draw_triangle(x0,y0,x2,y2,x3,y3,color,opac);
#ifdef cimg_use_board
            if (pboard) {
              board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opac*255));
              board.fillTriangle((float)x0,dimy()-(float)y0,(float)x1,dimy()-(float)y1,(float)x2,dimy()-(float)y2);
              board.fillTriangle((float)x0,dimy()-(float)y0,(float)x2,dimy()-(float)y2,(float)x3,dimy()-(float)y3);
            }
#endif
            break;
          case 3 :
            if (zbuffer)
              draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color.data,opac,lightprops(l)).
                draw_triangle(zbuffer,x0,y0,z0,x2,y2,z2,x3,y3,z3,color.data,opac,lightprops(l));
            else
              _draw_triangle(x0,y0,x1,y1,x2,y2,color.data,opac,lightprops(l)).
                _draw_triangle(x0,y0,x2,y2,x3,y3,color.data,opac,lightprops(l));
#ifdef cimg_use_board
            if (pboard) {
              const float lp = cimg::min(lightprops(l),1);
              board.setPenColorRGBi((unsigned char)(color[0]*lp),
                                     (unsigned char)(color[1]*lp),
                                     (unsigned char)(color[2]*lp),(unsigned char)(opac*255));
              board.fillTriangle((float)x0,dimy()-(float)y0,(float)x1,dimy()-(float)y1,(float)x2,dimy()-(float)y2);
              board.fillTriangle((float)x0,dimy()-(float)y0,(float)x2,dimy()-(float)y2,(float)x3,dimy()-(float)y3);
            }
#endif
            break;
          case 4 : {
            const float
              lightprop0 = lightprops(n0), lightprop1 = lightprops(n1),
              lightprop2 = lightprops(n2), lightprop3 = lightprops(n3);
            if (zbuffer)
              draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color,lightprop0,lightprop1,lightprop2,opac).
                draw_triangle(zbuffer,x0,y0,z0,x2,y2,z2,x3,y3,z3,color,lightprop0,lightprop2,lightprop3,opac);
            else
              draw_triangle(x0,y0,x1,y1,x2,y2,color,lightprop0,lightprop1,lightprop2,opac).
                draw_triangle(x0,y0,x2,y2,x3,y3,color,lightprop0,lightprop2,lightprop3,opac);
#ifdef cimg_use_board
            if (pboard) {
              board.setPenColorRGBi((unsigned char)(color[0]),
                                     (unsigned char)(color[1]),
                                     (unsigned char)(color[2]),
                                     (unsigned char)(opac*255));
              board.fillGouraudTriangle((float)x0,dimy()-(float)y0,lightprop0,
                                         (float)x1,dimy()-(float)y1,lightprop1,
                                         (float)x2,dimy()-(float)y2,lightprop2);
              board.fillGouraudTriangle((float)x0,dimy()-(float)y0,lightprop0,
                                         (float)x2,dimy()-(float)y2,lightprop2,
                                         (float)x3,dimy()-(float)y3,lightprop3);
            }
#endif
          } break;
          case 5 : {
            const unsigned int
              lx0 = (unsigned int)lightprops(n0,0), ly0 = (unsigned int)lightprops(n0,1),
              lx1 = (unsigned int)lightprops(n1,0), ly1 = (unsigned int)lightprops(n1,1),
              lx2 = (unsigned int)lightprops(n2,0), ly2 = (unsigned int)lightprops(n2,1),
              lx3 = (unsigned int)lightprops(n3,0), ly3 = (unsigned int)lightprops(n3,1);
            if (zbuffer)
              draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color,light_texture,lx0,ly0,lx1,ly1,lx2,ly2,opac).
                draw_triangle(zbuffer,x0,y0,z0,x2,y2,z2,x3,y3,z3,color,light_texture,lx0,ly0,lx2,ly2,lx3,ly3,opac);
            else
              draw_triangle(x0,y0,x1,y1,x2,y2,color,light_texture,lx0,ly0,lx1,ly1,lx2,ly2,opac).
                draw_triangle(x0,y0,x2,y2,x3,y3,color,light_texture,lx0,ly0,lx2,ly2,lx3,ly3,opac);
#ifdef cimg_use_board
            if (pboard) {
              const float
                l0 = light_texture((int)(light_texture.dimx()/2*(1+lx0)), (int)(light_texture.dimy()/2*(1+ly0))),
                l1 = light_texture((int)(light_texture.dimx()/2*(1+lx1)), (int)(light_texture.dimy()/2*(1+ly1))),
                l2 = light_texture((int)(light_texture.dimx()/2*(1+lx2)), (int)(light_texture.dimy()/2*(1+ly2))),
                l3 = light_texture((int)(light_texture.dimx()/2*(1+lx3)), (int)(light_texture.dimy()/2*(1+ly3)));
              board.setPenColorRGBi((unsigned char)(color[0]),
                                     (unsigned char)(color[1]),
                                     (unsigned char)(color[2]),
                                     (unsigned char)(opac*255));
              board.fillGouraudTriangle((float)x0,dimy()-(float)y0,l0,
                                         (float)x1,dimy()-(float)y1,l1,
                                         (float)x2,dimy()-(float)y2,l2);
              board.fillGouraudTriangle((float)x0,dimy()-(float)y0,l0,
                                         (float)x2,dimy()-(float)y2,l2,
                                         (float)x3,dimy()-(float)y3,l3);
            }
#endif
          } break;
          }
        } break;
        case 9 : { // Textured triangle
          const unsigned int
            n0 = (unsigned int)primitive[0],
            n1 = (unsigned int)primitive[1],
            n2 = (unsigned int)primitive[2],
            tx0 = (unsigned int)primitive[3],
            ty0 = (unsigned int)primitive[4],
            tx1 = (unsigned int)primitive[5],
            ty1 = (unsigned int)primitive[6],
            tx2 = (unsigned int)primitive[7],
            ty2 = (unsigned int)primitive[8];
          const int
            x0 = (int)projections(n0,0), y0 = (int)projections(n0,1),
            x1 = (int)projections(n1,0), y1 = (int)projections(n1,1),
            x2 = (int)projections(n2,0), y2 = (int)projections(n2,1);
          const float
            z0 = points(n0,2) + Z + focale,
            z1 = points(n1,2) + Z + focale,
            z2 = points(n2,2) + Z + focale;
          switch (render_type) {
          case 0 :
            draw_point(x0,y0,color.get_vector_at(tx0,ty0),opac).
              draw_point(x1,y1,color.get_vector_at(tx1,ty1),opac).
              draw_point(x2,y2,color.get_vector_at(tx2,ty2),opac);
#ifdef cimg_use_board
            if (pboard) {
              board.setPenColorRGBi(128,128,128,(unsigned char)(opac*255));
              board.drawCircle((float)x0,dimy()-(float)y0,0);
              board.drawCircle((float)x1,dimy()-(float)y1,0);
              board.drawCircle((float)x2,dimy()-(float)y2,0);
            }
#endif
            break;
          case 1 :
            if (zbuffer)
              draw_line(zbuffer,x0,y0,z0,x1,y1,z1,color,tx0,ty0,tx1,ty1,opac).
                draw_line(zbuffer,x0,y0,z0,x2,y2,z2,color,tx0,ty0,tx2,ty2,opac).
                draw_line(zbuffer,x1,y1,z1,x2,y2,z2,color,tx1,ty1,tx2,ty2,opac);
            else
              draw_line(x0,y0,z0,x1,y1,z1,color,tx0,ty0,tx1,ty1,opac).
                draw_line(x0,y0,z0,x2,y2,z2,color,tx0,ty0,tx2,ty2,opac).
                draw_line(x1,y1,z1,x2,y2,z2,color,tx1,ty1,tx2,ty2,opac);
#ifdef cimg_use_board
            if (pboard) {
              board.setPenColorRGBi(128,128,128,(unsigned char)(opac*255));
              board.drawLine((float)x0,dimy()-(float)y0,(float)x1,dimy()-(float)y1);
              board.drawLine((float)x0,dimy()-(float)y0,(float)x2,dimy()-(float)y2);
              board.drawLine((float)x1,dimy()-(float)y1,(float)x2,dimy()-(float)y2);
            }
#endif
            break;
          case 2 :
            if (zbuffer) draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,opac);
            else draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,opac);
#ifdef cimg_use_board
            if (pboard) {
              board.setPenColorRGBi(128,128,128,(unsigned char)(opac*255));
              board.fillTriangle((float)x0,dimy()-(float)y0,(float)x1,dimy()-(float)y1,(float)x2,dimy()-(float)y2);
            }
#endif
            break;
          case 3 :
            if (zbuffer) draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,opac,lightprops(l));
            else draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,opac,lightprops(l));
#ifdef cimg_use_board
            if (pboard) {
              const float lp = cimg::min(lightprops(l),1);
              board.setPenColorRGBi((unsigned char)(128*lp),
                                     (unsigned char)(128*lp),
                                     (unsigned char)(128*lp),
                                     (unsigned char)(opac*255));
              board.fillTriangle((float)x0,dimy()-(float)y0,(float)x1,dimy()-(float)y1,(float)x2,dimy()-(float)y2);
            }
#endif
            break;
          case 4 :
            if (zbuffer)
              draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,lightprops(n0),lightprops(n1),lightprops(n2),opac);
            else
              draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,lightprops(n0),lightprops(n1),lightprops(n2),opac);
#ifdef cimg_use_board
            if (pboard) {
              board.setPenColorRGBi(128,128,128,(unsigned char)(opac*255));
              board.fillGouraudTriangle((float)x0,dimy()-(float)y0,lightprops(n0),
                                         (float)x1,dimy()-(float)y1,lightprops(n1),
                                         (float)x2,dimy()-(float)y2,lightprops(n2));
            }
#endif
            break;
          case 5 :
            if (zbuffer)
              draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,light_texture,
                            (unsigned int)lightprops(n0,0), (unsigned int)lightprops(n0,1),
                            (unsigned int)lightprops(n1,0), (unsigned int)lightprops(n1,1),
                            (unsigned int)lightprops(n2,0), (unsigned int)lightprops(n2,1),
                            opac);
            else
              draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,light_texture,
                            (unsigned int)lightprops(n0,0), (unsigned int)lightprops(n0,1),
                            (unsigned int)lightprops(n1,0), (unsigned int)lightprops(n1,1),
                            (unsigned int)lightprops(n2,0), (unsigned int)lightprops(n2,1),
                            opac);
#ifdef cimg_use_board
            if (pboard) {
              const float
                l0 = light_texture((int)(light_texture.dimx()/2*(1+lightprops(n0,0))), (int)(light_texture.dimy()/2*(1+lightprops(n0,1)))),
                l1 = light_texture((int)(light_texture.dimx()/2*(1+lightprops(n1,0))), (int)(light_texture.dimy()/2*(1+lightprops(n1,1)))),
                l2 = light_texture((int)(light_texture.dimx()/2*(1+lightprops(n2,0))), (int)(light_texture.dimy()/2*(1+lightprops(n2,1))));
              board.setPenColorRGBi(128,128,128,(unsigned char)(opac*255));
              board.fillGouraudTriangle((float)x0,dimy()-(float)y0,l0,(float)x1,dimy()-(float)y1,l1,(float)x2,dimy()-(float)y2,l2);
            }
#endif
            break;
          }
        } break;
        case 12 : { // Textured rectangle
          const unsigned int
            n0 = (unsigned int)primitive[0],
            n1 = (unsigned int)primitive[1],
            n2 = (unsigned int)primitive[2],
            n3 = (unsigned int)primitive[3],
            tx0 = (unsigned int)primitive[4],
            ty0 = (unsigned int)primitive[5],
            tx1 = (unsigned int)primitive[6],
            ty1 = (unsigned int)primitive[7],
            tx2 = (unsigned int)primitive[8],
            ty2 = (unsigned int)primitive[9],
            tx3 = (unsigned int)primitive[10],
            ty3 = (unsigned int)primitive[11];
          const int
            x0 = (int)projections(n0,0), y0 = (int)projections(n0,1),
            x1 = (int)projections(n1,0), y1 = (int)projections(n1,1),
            x2 = (int)projections(n2,0), y2 = (int)projections(n2,1),
            x3 = (int)projections(n3,0), y3 = (int)projections(n3,1);
          const float
            z0 = points(n0,2) + Z + focale,
            z1 = points(n1,2) + Z + focale,
            z2 = points(n2,2) + Z + focale,
            z3 = points(n3,2) + Z + focale;
          switch (render_type) {
          case 0 :
            draw_point(x0,y0,color.get_vector_at(tx0,ty0),opac).
              draw_point(x1,y1,color.get_vector_at(tx1,ty1),opac).
              draw_point(x2,y2,color.get_vector_at(tx2,ty2),opac).
              draw_point(x3,y3,color.get_vector_at(tx3,ty3),opac);
#ifdef cimg_use_board
            if (pboard) {
              board.setPenColorRGBi(128,128,128,(unsigned char)(opac*255));
              board.drawCircle((float)x0,dimy()-(float)y0,0);
              board.drawCircle((float)x1,dimy()-(float)y1,0);
              board.drawCircle((float)x2,dimy()-(float)y2,0);
              board.drawCircle((float)x3,dimy()-(float)y3,0);
            }
#endif
            break;
          case 1 :
            if (zbuffer)
              draw_line(zbuffer,x0,y0,z0,x1,y1,z1,color,tx0,ty0,tx1,ty1,opac).
                draw_line(zbuffer,x1,y1,z1,x2,y2,z2,color,tx1,ty1,tx2,ty2,opac).
                draw_line(zbuffer,x2,y2,z2,x3,y3,z3,color,tx2,ty2,tx3,ty3,opac).
                draw_line(zbuffer,x3,y3,z3,x0,y0,z0,color,tx3,ty3,tx0,ty0,opac);
            else
              draw_line(x0,y0,z0,x1,y1,z1,color,tx0,ty0,tx1,ty1,opac).
                draw_line(x1,y1,z1,x2,y2,z2,color,tx1,ty1,tx2,ty2,opac).
                draw_line(x2,y2,z2,x3,y3,z3,color,tx2,ty2,tx3,ty3,opac).
                draw_line(x3,y3,z3,x0,y0,z0,color,tx3,ty3,tx0,ty0,opac);
#ifdef cimg_use_board
            if (pboard) {
              board.setPenColorRGBi(128,128,128,(unsigned char)(opac*255));
              board.drawLine((float)x0,dimy()-(float)y0,(float)x1,dimy()-(float)y1);
              board.drawLine((float)x1,dimy()-(float)y1,(float)x2,dimy()-(float)y2);
              board.drawLine((float)x2,dimy()-(float)y2,(float)x3,dimy()-(float)y3);
              board.drawLine((float)x3,dimy()-(float)y3,(float)x0,dimy()-(float)y0);
            }
#endif
            break;
          case 2 :
            if (zbuffer)
              draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,opac).
                draw_triangle(zbuffer,x0,y0,z0,x2,y2,z2,x3,y3,z3,color,tx0,ty0,tx2,ty2,tx3,ty3,opac);
            else
              draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,opac).
                draw_triangle(x0,y0,z0,x2,y2,z2,x3,y3,z3,color,tx0,ty0,tx2,ty2,tx3,ty3,opac);
#ifdef cimg_use_board
            if (pboard) {
              board.setPenColorRGBi(128,128,128,(unsigned char)(opac*255));
              board.fillTriangle((float)x0,dimy()-(float)y0,(float)x1,dimy()-(float)y1,(float)x2,dimy()-(float)y2);
              board.fillTriangle((float)x0,dimy()-(float)y0,(float)x2,dimy()-(float)y2,(float)x3,dimy()-(float)y3);
            }
#endif
            break;
          case 3 :
            if (zbuffer)
              draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,opac,lightprops(l)).
                draw_triangle(zbuffer,x0,y0,z0,x2,y2,z2,x3,y3,z3,color,tx0,ty0,tx2,ty2,tx3,ty3,opac,lightprops(l));
            else
              draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,opac,lightprops(l)).
                draw_triangle(x0,y0,z0,x2,y2,z2,x3,y3,z3,color,tx0,ty0,tx2,ty2,tx3,ty3,opac,lightprops(l));
#ifdef cimg_use_board
            if (pboard) {
              const float lp = cimg::min(lightprops(l),1);
              board.setPenColorRGBi((unsigned char)(128*lp),
                                     (unsigned char)(128*lp),
                                     (unsigned char)(128*lp),
                                     (unsigned char)(opac*255));
              board.fillTriangle((float)x0,dimy()-(float)y0,(float)x1,dimy()-(float)y1,(float)x2,dimy()-(float)y2);
              board.fillTriangle((float)x0,dimy()-(float)y0,(float)x2,dimy()-(float)y2,(float)x3,dimy()-(float)y3);
            }
#endif
            break;
          case 4 : {
            const float
              lightprop0 = lightprops(n0), lightprop1 = lightprops(n1),
              lightprop2 = lightprops(n2), lightprop3 = lightprops(n3);
            if (zbuffer)
              draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,lightprop0,lightprop1,lightprop2,opac).
                draw_triangle(zbuffer,x0,y0,z0,x2,y2,z2,x3,y3,z3,color,tx0,ty0,tx2,ty2,tx3,ty3,lightprop0,lightprop2,lightprop3,opac);
            else
              draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,lightprop0,lightprop1,lightprop2,opac).
                draw_triangle(x0,y0,z0,x2,y2,z2,x3,y3,z3,color,tx0,ty0,tx2,ty2,tx3,ty3,lightprop0,lightprop2,lightprop3,opac);
#ifdef cimg_use_board
            if (pboard) {
              board.setPenColorRGBi(128,128,128,(unsigned char)(opac*255));
              board.fillGouraudTriangle((float)x0,dimy()-(float)y0,lightprop0,
                                         (float)x1,dimy()-(float)y1,lightprop1,
                                         (float)x2,dimy()-(float)y2,lightprop2);
              board.fillGouraudTriangle((float)x0,dimy()-(float)y0,lightprop0,
                                         (float)x2,dimy()-(float)y2,lightprop2,
                                         (float)x3,dimy()-(float)y3,lightprop3);
            }
#endif
          } break;
          case 5 : {
            const unsigned int
              lx0 = (unsigned int)lightprops(n0,0), ly0 = (unsigned int)lightprops(n0,1),
              lx1 = (unsigned int)lightprops(n1,0), ly1 = (unsigned int)lightprops(n1,1),
              lx2 = (unsigned int)lightprops(n2,0), ly2 = (unsigned int)lightprops(n2,1),
              lx3 = (unsigned int)lightprops(n3,0), ly3 = (unsigned int)lightprops(n3,1);
            if (zbuffer)
              draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,light_texture,lx0,ly0,lx1,ly1,lx2,ly2,opac).
                draw_triangle(zbuffer,x0,y0,z0,x2,y2,z2,x3,y3,z3,color,tx0,ty0,tx2,ty2,tx3,ty3,light_texture,lx0,ly0,lx2,ly2,lx3,ly3,opac);
            else
              draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,light_texture,lx0,ly0,lx1,ly1,lx2,ly2,opac).
                draw_triangle(x0,y0,z0,x2,y2,z2,x3,y3,z3,color,tx0,ty0,tx2,ty2,tx3,ty3,light_texture,lx0,ly0,lx2,ly2,lx3,ly3,opac);
#ifdef cimg_use_board
            if (pboard) {
              const float
                l0 = light_texture((int)(light_texture.dimx()/2*(1+lx0)), (int)(light_texture.dimy()/2*(1+ly0))),
                l1 = light_texture((int)(light_texture.dimx()/2*(1+lx1)), (int)(light_texture.dimy()/2*(1+ly1))),
                l2 = light_texture((int)(light_texture.dimx()/2*(1+lx2)), (int)(light_texture.dimy()/2*(1+ly2))),
                l3 = light_texture((int)(light_texture.dimx()/2*(1+lx3)), (int)(light_texture.dimy()/2*(1+ly3)));
              board.setPenColorRGBi(128,128,128,(unsigned char)(opac*255));
              board.fillGouraudTriangle((float)x0,dimy()-(float)y0,l0,
                                         (float)x1,dimy()-(float)y1,l1,
                                         (float)x2,dimy()-(float)y2,l2);
              board.fillGouraudTriangle((float)x0,dimy()-(float)y0,l0,
                                         (float)x2,dimy()-(float)y2,l2,
                                         (float)x3,dimy()-(float)y3,l3);
            }
#endif
          } break;
          }
        } break;
        }
      }
      }
      return *this;
    }

    //! Draw a 3D object.
    /**
       \param X = X-coordinate of the 3d object position
       \param Y = Y-coordinate of the 3d object position
       \param Z = Z-coordinate of the 3d object position
       \param points = Image N*3 describing 3D point coordinates
       \param primitives = List of P primitives
       \param colors = List of P color (or textures)
       \param opacities = Image of P opacities
       \param render_type = Render type (0=Points, 1=Lines, 2=Faces (no light), 3=Faces (flat), 4=Faces(Gouraud)
       \param double_sided = Tell if object faces have two sides or are oriented.
       \param focale = length of the focale
       \param lightx = X-coordinate of the light
       \param lighty = Y-coordinate of the light
       \param lightz = Z-coordinate of the light
       \param specular_shine = Shininess of the object
    **/
    template<typename tp, typename tf, typename tc, typename to>
    CImg<T>& draw_object3d(const float x0, const float y0, const float z0,
                           const CImg<tp>& points, const CImgList<tf>& primitives,
                           const CImgList<tc>& colors, const CImgList<to>& opacities,
                           const unsigned int render_type=4,
                           const bool double_sided=false, const float focale=500,
                           const float lightx=0, const float lighty=0, const float lightz=-5000,
                           const float specular_light=0.2f, const float specular_shine=0.1f,
                           float *const zbuffer=0) {
      if (!points) return *this;
      return _draw_object3d(0,zbuffer,x0,y0,z0,points.height<3?points:points.get_resize(-100,3,1,1,0),points.width,
                            primitives,colors,opacities,opacities.size,
                            render_type,double_sided,focale,lightx,lighty,lightz,specular_light,specular_shine);
    }

#ifdef cimg_use_board
    template<typename tp, typename tf, typename tc, typename to>
    CImg<T>& draw_object3d(BoardLib::Board& board,
                           const float x0, const float y0, const float z0,
                           const CImg<tp>& points, const CImgList<tf>& primitives,
                           const CImgList<tc>& colors, const CImgList<to>& opacities,
                           const unsigned int render_type=4,
                           const bool double_sided=false, const float focale=500,
                           const float lightx=0, const float lighty=0, const float lightz=-5000,
                           const float specular_light=0.2f, const float specular_shine=0.1f,
                           float *const zbuffer=0) {
      if (!points) return *this;
      return _draw_object3d((void*)&board,zbuffer,x0,y0,z0,points.height<3?points:points.get_resize(-100,3,1,1,0),points.width,
                            primitives,colors,opacities,opacities.size,
                            render_type,double_sided,focale,lightx,lighty,lightz,specular_light,specular_shine);
    }
#endif

    //! Draw a 3D object.
    template<typename tp, typename tf, typename tc, typename to>
    CImg<T>& draw_object3d(const float x0, const float y0, const float z0,
                           const CImgList<tp>& points, const CImgList<tf>& primitives,
                           const CImgList<tc>& colors, const CImgList<to>& opacities,
                           const unsigned int render_type=4,
                           const bool double_sided=false, const float focale=500,
                           const float lightx=0, const float lighty=0, const float lightz=-5000,
                           const float specular_light=0.2f, const float specular_shine=0.1f,
                           float *const zbuffer=0) {
      if (!points) return *this;
      return _draw_object3d(0,zbuffer,x0,y0,z0,points,points.size,primitives,colors,opacities,opacities.size,
                            render_type,double_sided,focale,lightx,lighty,lightz,specular_light,specular_shine);
    }

#ifdef cimg_use_board
    template<typename tp, typename tf, typename tc, typename to>
    CImg<T>& draw_object3d(BoardLib::Board& board,
                           const float x0, const float y0, const float z0,
                           const CImgList<tp>& points, const CImgList<tf>& primitives,
                           const CImgList<tc>& colors, const CImgList<to>& opacities,
                           const unsigned int render_type=4,
                           const bool double_sided=false, const float focale=500,
                           const float lightx=0, const float lighty=0, const float lightz=-5000,
                           const float specular_light=0.2f, const float specular_shine=0.1f,
                           float *const zbuffer=0) {
      if (!points) return *this;
      return _draw_object3d((void*)&board,zbuffer,x0,y0,z0,points,points.size,primitives,colors,opacities,opacities.size,
                            render_type,double_sided,focale,lightx,lighty,lightz,specular_light,specular_shine);
    }
#endif

    //! Draw a 3D object.
    template<typename tp, typename tf, typename tc, typename to>
    CImg<T>& draw_object3d(const float x0, const float y0, const float z0,
                           const CImg<tp>& points, const CImgList<tf>& primitives,
                           const CImgList<tc>& colors, const CImg<to>& opacities,
                           const unsigned int render_type=4,
                           const bool double_sided=false, const float focale=500,
                           const float lightx=0, const float lighty=0, const float lightz=-5000,
                           const float specular_light=0.2f, const float specular_shine=0.1f,
                           float *const zbuffer=0) {
      if (!points) return *this;
      return _draw_object3d(0,zbuffer,x0,y0,z0,points.height<3?points:points.get_resize(-100,3,1,1,0),points.width,
                            primitives,colors,opacities,opacities.size(),
                            render_type,double_sided,focale,lightx,lighty,lightz,specular_light,specular_shine);
    }

#ifdef cimg_use_board
    template<typename tp, typename tf, typename tc, typename to>
    CImg<T>& draw_object3d(BoardLib::Board& board,
                           const float x0, const float y0, const float z0,
                           const CImg<tp>& points, const CImgList<tf>& primitives,
                           const CImgList<tc>& colors, const CImg<to>& opacities,
                           const unsigned int render_type=4,
                           const bool double_sided=false, const float focale=500,
                           const float lightx=0, const float lighty=0, const float lightz=-5000,
                           const float specular_light=0.2f, const float specular_shine=0.1f,
                           float *const zbuffer=0) {
      if (!points) return *this;
      return _draw_object3d((void*)&board,zbuffer,x0,y0,z0,points.height<3?points:points.get_resize(-100,3,1,1,0),points.width
                            ,primitives,colors,opacities,opacities.size(),
                            render_type,double_sided,focale,lightx,lighty,lightz,specular_light,specular_shine);
    }
#endif

    //! Draw a 3D object.
    template<typename tp, typename tf, typename tc, typename to>
    CImg<T>& draw_object3d(const float x0, const float y0, const float z0,
                           const CImgList<tp>& points, const CImgList<tf>& primitives,
                           const CImgList<tc>& colors, const CImg<to>& opacities,
                           const unsigned int render_type=4,
                           const bool double_sided=false, const float focale=500,
                           const float lightx=0, const float lighty=0, const float lightz=-5000,
                           const float specular_light=0.2f, const float specular_shine=0.1f,
                           float *const zbuffer=0) {
      if (!points) return *this;
      return _draw_object3d(0,zbuffer,x0,y0,z0,points,points.size,primitives,colors,opacities,opacities.size(),
                            render_type,double_sided,focale,lightx,lighty,lightz,specular_light,specular_shine);
    }

#ifdef cimg_use_board
    template<typename tp, typename tf, typename tc, typename to>
    CImg<T>& draw_object3d(BoardLib::Board& board,
                           const float x0, const float y0, const float z0,
                           const CImgList<tp>& points, const CImgList<tf>& primitives,
                           const CImgList<tc>& colors, const CImg<to>& opacities,
                           const unsigned int render_type=4,
                           const bool double_sided=false, const float focale=500,
                           const float lightx=0, const float lighty=0, const float lightz=-5000,
                           const float specular_light=0.2f, const float specular_shine=0.1f,
                           float *const zbuffer=0) {
      if (!points) return *this;
      return _draw_object3d((void*)&board,zbuffer,x0,y0,z0,points,points.size,primitives,colors,opacities,opacities.size(),
                            render_type,double_sided,focale,lightx,lighty,lightz,specular_light,specular_shine);
    }
#endif

    //! Draw a 3D object.
    template<typename tp, typename tf, typename tc>
    CImg<T>& draw_object3d(const float x0, const float y0, const float z0,
                           const tp& points, const CImgList<tf>& primitives,
                           const CImgList<tc>& colors,
                           const unsigned int render_type=4,
                           const bool double_sided=false, const float focale=500,
                           const float lightx=0, const float lighty=0, const float lightz=-5000,
                           const float specular_light=0.2f, const float specular_shine=0.1f,
                           float *const zbuffer=0) {
      static const CImg<floatT> opacities;
      return draw_object3d(x0,y0,z0,points,primitives,colors,opacities,
                           render_type,double_sided,focale,lightx,lighty,lightz,specular_light,specular_shine,zbuffer);
    }

#ifdef cimg_use_board
    template<typename tp, typename tf, typename tc, typename to>
    CImg<T>& draw_object3d(BoardLib::Board& board,
                           const float x0, const float y0, const float z0,
                           const tp& points, const CImgList<tf>& primitives,
                           const CImgList<tc>& colors,
                           const unsigned int render_type=4,
                           const bool double_sided=false, const float focale=500,
                           const float lightx=0, const float lighty=0, const float lightz=-5000,
                           const float specular_light=0.2f, const float specular_shine=0.1f,
                           float *const zbuffer=0) {
      static const CImg<floatT> opacities;
      return draw_object3d(x0,y0,z0,points,primitives,colors,opacities,
                           render_type,double_sided,focale,lightx,lighty,lightz,specular_light,specular_shine,zbuffer);
    }
#endif

    //@}
    //----------------------------
    //
    //! \name Image Filtering
    //@{
    //----------------------------

    //! Compute the correlation of the instance image by a mask.
    /**
       The correlation of the instance image \p *this by the mask \p mask is defined to be :

       res(x,y,z) = sum_{i,j,k} (*this)(x+i,y+j,z+k)*mask(i,j,k)

       \param mask = the correlation kernel.
       \param cond = the border condition type (0=zero, 1=dirichlet)
       \param weighted_correl = enable local normalization.
    **/
    template<typename t>
    CImg<T>& correlate(const CImg<t>& mask, const unsigned int cond=1, const bool weighted_correl=false) {
      return get_correlate(mask,cond,weighted_correl).transfer_to(*this);
    }

    template<typename t>
    CImg<typename cimg::superset2<T,t,float>::type> get_correlate(const CImg<t>& mask, const unsigned int cond=1,
                                                                  const bool weighted_correl=false) const {
      typedef typename cimg::superset2<T,t,float>::type Ttfloat;
      if (is_empty()) return *this;
      if (!mask || mask.dim!=1)
        throw CImgArgumentException("CImg<%s>::correlate() : Specified mask (%u,%u,%u,%u,%p) is not scalar.",
                                    pixel_type(),mask.width,mask.height,mask.depth,mask.dim,mask.data);
      CImg<Ttfloat> dest(width,height,depth,dim);
      if (cond && mask.width==mask.height && ((mask.depth==1 && mask.width<=5) || (mask.depth==mask.width && mask.width<=3))) {
        // A special optimization is done for 2x2, 3x3, 4x4, 5x5, 2x2x2 and 3x3x3 mask (with cond=1)
        switch (mask.depth) {
        case 3 : {
          T I[27] = { 0 };
          cimg_forZV(*this,z,v) cimg_for3x3x3(*this,x,y,z,v,I) dest(x,y,z,v) = (Ttfloat)
            (I[ 0]*mask[ 0] + I[ 1]*mask[ 1] + I[ 2]*mask[ 2] +
             I[ 3]*mask[ 3] + I[ 4]*mask[ 4] + I[ 5]*mask[ 5] +
             I[ 6]*mask[ 6] + I[ 7]*mask[ 7] + I[ 8]*mask[ 8] +
             I[ 9]*mask[ 9] + I[10]*mask[10] + I[11]*mask[11] +
             I[12]*mask[12] + I[13]*mask[13] + I[14]*mask[14] +
             I[15]*mask[15] + I[16]*mask[16] + I[17]*mask[17] +
             I[18]*mask[18] + I[19]*mask[19] + I[20]*mask[20] +
             I[21]*mask[21] + I[22]*mask[22] + I[23]*mask[23] +
             I[24]*mask[24] + I[25]*mask[25] + I[26]*mask[26]);
          if (weighted_correl) cimg_forZV(*this,z,v) cimg_for3x3x3(*this,x,y,z,v,I) {
            const double weight = (double)(I[ 0]*I[ 0] + I[ 1]*I[ 1] + I[ 2]*I[ 2] +
                                           I[ 3]*I[ 3] + I[ 4]*I[ 4] + I[ 5]*I[ 5] +
                                           I[ 6]*I[ 6] + I[ 7]*I[ 7] + I[ 8]*I[ 8] +
                                           I[ 9]*I[ 9] + I[10]*I[10] + I[11]*I[11] +
                                           I[12]*I[12] + I[13]*I[13] + I[14]*I[14] +
                                           I[15]*I[15] + I[16]*I[16] + I[17]*I[17] +
                                           I[18]*I[18] + I[19]*I[19] + I[20]*I[20] +
                                           I[21]*I[21] + I[22]*I[22] + I[23]*I[23] +
                                           I[24]*I[24] + I[25]*I[25] + I[26]*I[26]);
            if (weight>0) dest(x,y,z,v)/=(Ttfloat)cimg_std::sqrt(weight);
          }
        } break;
        case 2 : {
          T I[8] = { 0 };
          cimg_forZV(*this,z,v) cimg_for2x2x2(*this,x,y,z,v,I) dest(x,y,z,v) = (Ttfloat)
            (I[0]*mask[0] + I[1]*mask[1] +
             I[2]*mask[2] + I[3]*mask[3] +
             I[4]*mask[4] + I[5]*mask[5] +
             I[6]*mask[6] + I[7]*mask[7]);
          if (weighted_correl) cimg_forZV(*this,z,v) cimg_for2x2x2(*this,x,y,z,v,I) {
            const double weight = (double)(I[0]*I[0] + I[1]*I[1] +
                                           I[2]*I[2] + I[3]*I[3] +
                                           I[4]*I[4] + I[5]*I[5] +
                                           I[6]*I[6] + I[7]*I[7]);
            if (weight>0) dest(x,y,z,v)/=(Ttfloat)cimg_std::sqrt(weight);
          }
        } break;
        default :
        case 1 :
          switch (mask.width) {
          case 6 : {
            T I[36] = { 0 };
            cimg_forZV(*this,z,v) cimg_for6x6(*this,x,y,z,v,I) dest(x,y,z,v) = (Ttfloat)
              (I[ 0]*mask[ 0] + I[ 1]*mask[ 1] + I[ 2]*mask[ 2] + I[ 3]*mask[ 3] + I[ 4]*mask[ 4] + I[ 5]*mask[ 5] +
               I[ 6]*mask[ 6] + I[ 7]*mask[ 7] + I[ 8]*mask[ 8] + I[ 9]*mask[ 9] + I[10]*mask[10] + I[11]*mask[11] +
               I[12]*mask[12] + I[13]*mask[13] + I[14]*mask[14] + I[15]*mask[15] + I[16]*mask[16] + I[17]*mask[17] +
               I[18]*mask[18] + I[19]*mask[19] + I[20]*mask[20] + I[21]*mask[21] + I[22]*mask[22] + I[23]*mask[23] +
               I[24]*mask[24] + I[25]*mask[25] + I[26]*mask[26] + I[27]*mask[27] + I[28]*mask[28] + I[29]*mask[29] +
               I[30]*mask[30] + I[31]*mask[31] + I[32]*mask[32] + I[33]*mask[33] + I[34]*mask[34] + I[35]*mask[35]);
            if (weighted_correl) cimg_forZV(*this,z,v) cimg_for5x5(*this,x,y,z,v,I) {
              const double weight = (double)(I[ 0]*I[ 0] + I[ 1]*I[ 1] + I[ 2]*I[ 2] + I[ 3]*I[ 3] + I[ 4]*I[ 4] + I[ 5]*I[ 5] +
                                             I[ 6]*I[ 6] + I[ 7]*I[ 7] + I[ 8]*I[ 8] + I[ 9]*I[ 9] + I[10]*I[10] + I[11]*I[11] +
                                             I[12]*I[12] + I[13]*I[13] + I[14]*I[14] + I[15]*I[15] + I[16]*I[16] + I[17]*I[17] +
                                             I[18]*I[18] + I[19]*I[19] + I[20]*I[20] + I[21]*I[21] + I[22]*I[22] + I[23]*I[23] +
                                             I[24]*I[24] + I[25]*I[25] + I[26]*I[26] + I[27]*I[27] + I[28]*I[28] + I[29]*I[29] +
                                             I[30]*I[30] + I[31]*I[31] + I[32]*I[32] + I[33]*I[33] + I[34]*I[34] + I[35]*I[35]);
              if (weight>0) dest(x,y,z,v)/=(Ttfloat)cimg_std::sqrt(weight);
            }
          } break;
          case 5 : {
            T I[25] = { 0 };
            cimg_forZV(*this,z,v) cimg_for5x5(*this,x,y,z,v,I) dest(x,y,z,v) = (Ttfloat)
              (I[ 0]*mask[ 0] + I[ 1]*mask[ 1] + I[ 2]*mask[ 2] + I[ 3]*mask[ 3] + I[ 4]*mask[ 4] +
               I[ 5]*mask[ 5] + I[ 6]*mask[ 6] + I[ 7]*mask[ 7] + I[ 8]*mask[ 8] + I[ 9]*mask[ 9] +
               I[10]*mask[10] + I[11]*mask[11] + I[12]*mask[12] + I[13]*mask[13] + I[14]*mask[14] +
               I[15]*mask[15] + I[16]*mask[16] + I[17]*mask[17] + I[18]*mask[18] + I[19]*mask[19] +
               I[20]*mask[20] + I[21]*mask[21] + I[22]*mask[22] + I[23]*mask[23] + I[24]*mask[24]);
            if (weighted_correl) cimg_forZV(*this,z,v) cimg_for5x5(*this,x,y,z,v,I) {
              const double weight = (double)(I[ 0]*I[ 0] + I[ 1]*I[ 1] + I[ 2]*I[ 2] + I[ 3]*I[ 3] + I[ 4]*I[ 4] +
                                             I[ 5]*I[ 5] + I[ 6]*I[ 6] + I[ 7]*I[ 7] + I[ 8]*I[ 8] + I[ 9]*I[ 9] +
                                             I[10]*I[10] + I[11]*I[11] + I[12]*I[12] + I[13]*I[13] + I[14]*I[14] +
                                             I[15]*I[15] + I[16]*I[16] + I[17]*I[17] + I[18]*I[18] + I[19]*I[19] +
                                             I[20]*I[20] + I[21]*I[21] + I[22]*I[22] + I[23]*I[23] + I[24]*I[24]);
              if (weight>0) dest(x,y,z,v)/=(Ttfloat)cimg_std::sqrt(weight);
            }
          } break;
          case 4 : {
            T I[16] = { 0 };
            cimg_forZV(*this,z,v) cimg_for4x4(*this,x,y,z,v,I) dest(x,y,z,v) = (Ttfloat)
              (I[ 0]*mask[ 0] + I[ 1]*mask[ 1] + I[ 2]*mask[ 2] + I[ 3]*mask[ 3] +
               I[ 4]*mask[ 4] + I[ 5]*mask[ 5] + I[ 6]*mask[ 6] + I[ 7]*mask[ 7] +
               I[ 8]*mask[ 8] + I[ 9]*mask[ 9] + I[10]*mask[10] + I[11]*mask[11] +
               I[12]*mask[12] + I[13]*mask[13] + I[14]*mask[14] + I[15]*mask[15]);
            if (weighted_correl) cimg_forZV(*this,z,v) cimg_for4x4(*this,x,y,z,v,I) {
              const double weight = (double)(I[ 0]*I[ 0] + I[ 1]*I[ 1] + I[ 2]*I[ 2] + I[ 3]*I[ 3] +
                                             I[ 4]*I[ 4] + I[ 5]*I[ 5] + I[ 6]*I[ 6] + I[ 7]*I[ 7] +
                                             I[ 8]*I[ 8] + I[ 9]*I[ 9] + I[10]*I[10] + I[11]*I[11] +
                                             I[12]*I[12] + I[13]*I[13] + I[14]*I[14] + I[15]*I[15]);
              if (weight>0) dest(x,y,z,v)/=(Ttfloat)cimg_std::sqrt(weight);
            }
          } break;
          case 3 : {
            T I[9] = { 0 };
            cimg_forZV(*this,z,v) cimg_for3x3(*this,x,y,z,v,I) dest(x,y,z,v) = (Ttfloat)
              (I[0]*mask[0] + I[1]*mask[1] + I[2]*mask[2] +
               I[3]*mask[3] + I[4]*mask[4] + I[5]*mask[5] +
               I[6]*mask[6] + I[7]*mask[7] + I[8]*mask[8]);
            if (weighted_correl) cimg_forZV(*this,z,v) cimg_for3x3(*this,x,y,z,v,I) {
              const double weight = (double)(I[0]*I[0] + I[1]*I[1] + I[2]*I[2] +
                                             I[3]*I[3] + I[4]*I[4] + I[5]*I[5] +
                                             I[6]*I[6] + I[7]*I[7] + I[8]*I[8]);
              if (weight>0) dest(x,y,z,v)/=(Ttfloat)cimg_std::sqrt(weight);
            }
          } break;
          case 2 : {
            T I[4] = { 0 };
            cimg_forZV(*this,z,v) cimg_for2x2(*this,x,y,z,v,I) dest(x,y,z,v) = (Ttfloat)
              (I[0]*mask[0] + I[1]*mask[1] +
               I[2]*mask[2] + I[3]*mask[3]);
            if (weighted_correl) cimg_forZV(*this,z,v) cimg_for2x2(*this,x,y,z,v,I) {
              const double weight = (double)(I[0]*I[0] + I[1]*I[1] +
                                             I[2]*I[2] + I[3]*I[3]);
              if (weight>0) dest(x,y,z,v)/=(Ttfloat)cimg_std::sqrt(weight);
            }
          } break;
          case 1 : (dest.assign(*this))*=mask(0); break;
          }
        }
      } else { // Generic version for other masks
        const int
          mx2 = mask.dimx()/2, my2 = mask.dimy()/2, mz2 = mask.dimz()/2,
          mx1 = mx2 - 1 + (mask.dimx()%2), my1 = my2 - 1 + (mask.dimy()%2), mz1 = mz2 - 1 + (mask.dimz()%2),
          mxe = dimx() - mx2, mye = dimy() - my2, mze = dimz() - mz2;
        cimg_forV(*this,v)
          if (!weighted_correl) { // Classical correlation
            for (int z = mz1; z<mze; ++z) for (int y = my1; y<mye; ++y) for (int x = mx1; x<mxe; ++x) {
              Ttfloat val = 0;
              for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm)
                val+=(*this)(x+xm,y+ym,z+zm,v)*mask(mx1+xm,my1+ym,mz1+zm);
              dest(x,y,z,v) = (Ttfloat)val;
            }
            if (cond)
              cimg_forYZV(*this,y,z,v)
                for (int x = 0; x<dimx(); (y<my1 || y>=mye || z<mz1 || z>=mze)?++x:((x<mx1-1 || x>=mxe)?++x:(x=mxe))) {
                  Ttfloat val = 0;
                  for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm)
                    val+=_atXYZ(x+xm,y+ym,z+zm,v)*mask(mx1+xm,my1+ym,mz1+zm);
                  dest(x,y,z,v) = (Ttfloat)val;
                }
            else
              cimg_forYZV(*this,y,z,v)
                for (int x = 0; x<dimx(); (y<my1 || y>=mye || z<mz1 || z>=mze)?++x:((x<mx1-1 || x>=mxe)?++x:(x=mxe))) {
                  Ttfloat val = 0;
                  for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm)
                    val+=atXYZ(x+xm,y+ym,z+zm,v,0)*mask(mx1+xm,my1+ym,mz1+zm);
                  dest(x,y,z,v) = (Ttfloat)val;
                }
          } else { // Weighted correlation
            for (int z = mz1; z<mze; ++z) for (int y = my1; y<mye; ++y) for (int x = mx1; x<mxe; ++x) {
              Ttfloat val = 0, weight = 0;
              for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm) {
                const Ttfloat cval = (Ttfloat)(*this)(x+xm,y+ym,z+zm,v);
                val+=cval*mask(mx1+xm,my1+ym,mz1+zm);
                weight+=cval*cval;
              }
              dest(x,y,z,v) = (weight>(Ttfloat)0)?(Ttfloat)(val/cimg_std::sqrt((double)weight)):(Ttfloat)0;
            }
            if (cond)
              cimg_forYZV(*this,y,z,v)
                for (int x = 0; x<dimx(); (y<my1 || y>=mye || z<mz1 || z>=mze)?++x:((x<mx1-1 || x>=mxe)?++x:(x=mxe))) {
                  Ttfloat val = 0, weight = 0;
                  for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm) {
                    const Ttfloat cval = (Ttfloat)_atXYZ(x+xm,y+ym,z+zm,v);
                    val+=cval*mask(mx1+xm,my1+ym,mz1+zm);
                    weight+=cval*cval;
                  }
                  dest(x,y,z,v) = (weight>(Ttfloat)0)?(Ttfloat)(val/cimg_std::sqrt((double)weight)):(Ttfloat)0;
                }
            else
              cimg_forYZV(*this,y,z,v)
                for (int x = 0; x<dimx(); (y<my1 || y>=mye || z<mz1 || z>=mze)?++x:((x<mx1-1 || x>=mxe)?++x:(x=mxe))) {
                  Ttfloat val = 0, weight = 0;
                  for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm) {
                    const Ttfloat cval = (Ttfloat)atXYZ(x+xm,y+ym,z+zm,v,0);
                    val+=cval*mask(mx1+xm,my1+ym,mz1+zm);
                    weight+=cval*cval;
                  }
                  dest(x,y,z,v) = (weight>(Ttfloat)0)?(Ttfloat)(val/cimg_std::sqrt((double)weight)):(Ttfloat)0;
                }
          }
      }
      return dest;
    }

    //! Compute the convolution of the image by a mask.
    /**
       The result \p res of the convolution of an image \p img by a mask \p mask is defined to be :

       res(x,y,z) = sum_{i,j,k} img(x-i,y-j,z-k)*mask(i,j,k)

       \param mask = the correlation kernel.
       \param cond = the border condition type (0=zero, 1=dirichlet)
       \param weighted_convol = enable local normalization.
    **/
    template<typename t>
    CImg<T>& convolve(const CImg<t>& mask, const unsigned int cond=1, const bool weighted_convol=false) {
      return get_convolve(mask,cond,weighted_convol).transfer_to(*this);
    }

    template<typename t>
    CImg<typename cimg::superset2<T,t,float>::type> get_convolve(const CImg<t>& mask, const unsigned int cond=1,
                                                                 const bool weighted_convol=false) const {
      typedef typename cimg::superset2<T,t,float>::type Ttfloat;
      if (is_empty()) return *this;
      if (!mask || mask.dim!=1)
        throw CImgArgumentException("CImg<%s>::convolve() : Specified mask (%u,%u,%u,%u,%p) is not scalar.",
                                    pixel_type(),mask.width,mask.height,mask.depth,mask.dim,mask.data);
      return get_correlate(CImg<t>(mask.ptr(),mask.size(),1,1,1,true).get_mirror('x').resize(mask,-1),cond,weighted_convol);
    }

    //! Return the erosion of the image by a structuring element.
    template<typename t>
    CImg<T>& erode(const CImg<t>& mask, const unsigned int cond=1, const bool weighted_erosion=false) {
      return get_erode(mask,cond,weighted_erosion).transfer_to(*this);
    }

    template<typename t>
    CImg<typename cimg::superset<T,t>::type> get_erode(const CImg<t>& mask, const unsigned int cond=1,
                                                       const bool weighted_erosion=false) const {
      typedef typename cimg::superset<T,t>::type Tt;
      if (is_empty()) return *this;
      if (!mask || mask.dim!=1)
        throw CImgArgumentException("CImg<%s>::erode() : Specified mask (%u,%u,%u,%u,%p) is not a scalar image.",
                                    pixel_type(),mask.width,mask.height,mask.depth,mask.dim,mask.data);
      CImg<Tt> dest(width,height,depth,dim);
      const int
        mx2 = mask.dimx()/2, my2 = mask.dimy()/2, mz2 = mask.dimz()/2,
        mx1 = mx2 - 1 + (mask.dimx()%2), my1 = my2 - 1 + (mask.dimy()%2), mz1 = mz2 - 1 + (mask.dimz()%2),
        mxe = dimx() - mx2, mye = dimy() - my2, mze = dimz() - mz2;
      cimg_forV(*this,v)
        if (!weighted_erosion) { // Classical erosion
          for (int z = mz1; z<mze; ++z) for (int y = my1; y<mye; ++y) for (int x = mx1; x<mxe; ++x) {
            Tt min_val = cimg::type<Tt>::max();
            for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm) {
              const Tt cval = (Tt)(*this)(x+xm,y+ym,z+zm,v);
              if (mask(mx1+xm,my1+ym,mz1+zm) && cval<min_val) min_val = cval;
            }
            dest(x,y,z,v) = min_val;
          }
          if (cond)
            cimg_forYZV(*this,y,z,v)
              for (int x = 0; x<dimx(); (y<my1 || y>=mye || z<mz1 || z>=mze)?++x:((x<mx1-1 || x>=mxe)?++x:(x=mxe))) {
                Tt min_val = cimg::type<Tt>::max();
                for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm) {
                  const T cval = (Tt)_atXYZ(x+xm,y+ym,z+zm,v);
                  if (mask(mx1+xm,my1+ym,mz1+zm) && cval<min_val) min_val = cval;
                }
                dest(x,y,z,v) = min_val;
              }
          else
            cimg_forYZV(*this,y,z,v)
              for (int x = 0; x<dimx(); (y<my1 || y>=mye || z<mz1 || z>=mze)?++x:((x<mx1-1 || x>=mxe)?++x:(x=mxe))) {
                Tt min_val = cimg::type<Tt>::max();
                for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm) {
                  const T cval = (Tt)atXYZ(x+xm,y+ym,z+zm,v,0);
                  if (mask(mx1+xm,my1+ym,mz1+zm) && cval<min_val) min_val = cval;
                }
                dest(x,y,z,v) = min_val;
              }
        } else { // Weighted erosion
          for (int z = mz1; z<mze; ++z) for (int y = my1; y<mye; ++y) for (int x = mx1; x<mxe; ++x) {
            Tt min_val = cimg::type<Tt>::max();
            for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm) {
              const t mval = mask(mx1+xm,my1+ym,mz1+zm);
              const Tt cval = (Tt)((*this)(x+xm,y+ym,z+zm,v) + mval);
              if (mval && cval<min_val) min_val = cval;
            }
            dest(x,y,z,v) = min_val;
          }
          if (cond)
            cimg_forYZV(*this,y,z,v)
              for (int x = 0; x<dimx(); (y<my1 || y>=mye || z<mz1 || z>=mze)?++x:((x<mx1-1 || x>=mxe)?++x:(x=mxe))) {
                Tt min_val = cimg::type<Tt>::max();
                for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm) {
                  const t mval = mask(mx1+xm,my1+ym,mz1+zm);
                  const Tt cval = (Tt)(_atXYZ(x+xm,y+ym,z+zm,v) + mval);
                  if (mval && cval<min_val) min_val = cval;
                }
                dest(x,y,z,v) = min_val;
              }
          else
            cimg_forYZV(*this,y,z,v)
              for (int x = 0; x<dimx(); (y<my1 || y>=mye || z<mz1 || z>=mze)?++x:((x<mx1-1 || x>=mxe)?++x:(x=mxe))) {
                Tt min_val = cimg::type<Tt>::max();
                for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm) {
                  const t mval = mask(mx1+xm,my1+ym,mz1+zm);
                  const Tt cval = (Tt)(atXYZ(x+xm,y+ym,z+zm,v,0) + mval);
                  if (mval && cval<min_val) min_val = cval;
                }
                dest(x,y,z,v) = min_val;
              }
        }
      return dest;
    }

    //! Erode the image by a square structuring element of size n.
    CImg<T>& erode(const unsigned int n, const unsigned int cond=1) {
      if (n<2) return *this;
      return get_erode(n,cond).transfer_to(*this);
    }

    CImg<T> get_erode(const unsigned int n, const unsigned int cond=1) const {
      static CImg<T> mask;
      if (n<2) return *this;
      if (mask.width!=n) mask.assign(n,n,1,1,1);
      const CImg<T> res = get_erode(mask,cond,false);
      if (n>20) mask.assign();
      return res;
    }

    //! Dilate the image by a structuring element.
    template<typename t>
    CImg<T>& dilate(const CImg<t>& mask, const unsigned int cond=1, const bool weighted_dilatation=false) {
      return get_dilate(mask,cond,weighted_dilatation).transfer_to(*this);
    }

    template<typename t>
    CImg<typename cimg::superset<T,t>::type> get_dilate(const CImg<t>& mask, const unsigned int cond=1,
                                                        const bool weighted_dilatation=false) const {
      typedef typename cimg::superset<T,t>::type Tt;
      if (is_empty()) return *this;
      if (!mask || mask.dim!=1)
        throw CImgArgumentException("CImg<%s>::dilate() : Specified mask (%u,%u,%u,%u,%p) is not a scalar image.",
                                    pixel_type(),mask.width,mask.height,mask.depth,mask.dim,mask.data);
      CImg<Tt> dest(width,height,depth,dim);
      const int
        mx2 = mask.dimx()/2, my2 = mask.dimy()/2, mz2 = mask.dimz()/2,
        mx1 = mx2 - 1 + (mask.dimx()%2), my1 = my2 - 1 + (mask.dimy()%2), mz1 = mz2 - 1 + (mask.dimz()%2),
        mxe = dimx() - mx2, mye = dimy() - my2, mze = dimz() - mz2;
      cimg_forV(*this,v)
        if (!weighted_dilatation) { // Classical dilatation
          for (int z = mz1; z<mze; ++z) for (int y = my1; y<mye; ++y) for (int x = mx1; x<mxe; ++x) {
            Tt max_val = cimg::type<Tt>::min();
            for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm) {
              const Tt cval = (Tt)(*this)(x+xm,y+ym,z+zm,v);
              if (mask(mx1+xm,my1+ym,mz1+zm) && cval>max_val) max_val = cval;
            }
            dest(x,y,z,v) = max_val;
          }
          if (cond)
            cimg_forYZV(*this,y,z,v)
              for (int x = 0; x<dimx(); (y<my1 || y>=mye || z<mz1 || z>=mze)?++x:((x<mx1-1 || x>=mxe)?++x:(x=mxe))) {
                Tt max_val = cimg::type<Tt>::min();
                for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm) {
                  const T cval = (Tt)_atXYZ(x+xm,y+ym,z+zm,v);
                  if (mask(mx1+xm,my1+ym,mz1+zm) && cval>max_val) max_val = cval;
                }
                dest(x,y,z,v) = max_val;
              }
          else
            cimg_forYZV(*this,y,z,v)
              for (int x = 0; x<dimx(); (y<my1 || y>=mye || z<mz1 || z>=mze)?++x:((x<mx1-1 || x>=mxe)?++x:(x=mxe))) {
                Tt max_val = cimg::type<Tt>::min();
                for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm) {
                  const T cval = (Tt)atXYZ(x+xm,y+ym,z+zm,v,0);
                  if (mask(mx1+xm,my1+ym,mz1+zm) && cval>max_val) max_val = cval;
                }
                dest(x,y,z,v) = max_val;
              }
        } else { // Weighted dilatation
          for (int z = mz1; z<mze; ++z) for (int y = my1; y<mye; ++y) for (int x = mx1; x<mxe; ++x) {
            Tt max_val = cimg::type<Tt>::min();
            for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm) {
              const t mval = mask(mx1+xm,my1+ym,mz1+zm);
              const Tt cval = (Tt)((*this)(x+xm,y+ym,z+zm,v) - mval);
              if (mval && cval>max_val) max_val = cval;
            }
            dest(x,y,z,v) = max_val;
          }
          if (cond)
            cimg_forYZV(*this,y,z,v)
              for (int x = 0; x<dimx(); (y<my1 || y>=mye || z<mz1 || z>=mze)?++x:((x<mx1-1 || x>=mxe)?++x:(x=mxe))) {
                Tt max_val = cimg::type<Tt>::min();
                for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm) {
                  const t mval = mask(mx1+xm,my1+ym,mz1+zm);
                  const Tt cval = (Tt)(_atXYZ(x+xm,y+ym,z+zm,v) - mval);
                  if (mval && cval>max_val) max_val = cval;
                }
                dest(x,y,z,v) = max_val;
              }
          else
            cimg_forYZV(*this,y,z,v)
              for (int x = 0; x<dimx(); (y<my1 || y>=mye || z<mz1 || z>=mze)?++x:((x<mx1-1 || x>=mxe)?++x:(x=mxe))) {
                Tt max_val = cimg::type<Tt>::min();
                for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm) {
                  const t mval = mask(mx1+xm,my1+ym,mz1+zm);
                  const Tt cval = (Tt)(atXYZ(x+xm,y+ym,z+zm,v,0) - mval);
                  if (mval && cval>max_val) max_val = cval;
                }
                dest(x,y,z,v) = max_val;
              }
        }
      return dest;
    }

    //! Dilate the image by a square structuring element of size n.
    CImg<T>& dilate(const unsigned int n, const unsigned int cond=1) {
      if (n<2) return *this;
      return get_dilate(n,cond).transfer_to(*this);
    }

    CImg<T> get_dilate(const unsigned int n, const unsigned int cond=1) const {
      static CImg<T> mask;
      if (n<2) return *this;
      if (mask.width!=n) mask.assign(n,n,1,1,1);
      const CImg<T> res = get_dilate(mask,cond,false);
      if (n>20) mask.assign();
      return res;
    }

    //! Add noise to the image.
    /**
       \param sigma = power of the noise. if sigma<0, it corresponds to the percentage of the maximum image value.
       \param ntype = noise type. can be 0=gaussian, 1=uniform or 2=Salt and Pepper, 3=Poisson, 4=Rician.
       \return A noisy version of the instance image.
    **/
    CImg<T>& noise(const double sigma, const unsigned int noise_type=0) {
      if (!is_empty()) {
        double nsigma = sigma, max = (double)cimg::type<T>::max(), min = (double)cimg::type<T>::min();
        Tfloat m = 0, M = 0;
        if (nsigma==0 && noise_type!=3) return *this;
        if (nsigma<0 || noise_type==2) m = (Tfloat)minmax(M);
        if (nsigma<0) nsigma = -nsigma*(M-m)/100.0;
        switch (noise_type) {
        case 0 : { // Gaussian noise
          cimg_for(*this,ptr,T) {
            double val = *ptr + nsigma*cimg::grand();
            if (val>max) val = max;
            if (val<min) val = min;
            *ptr = (T)val;
          }
        } break;
        case 1 : { // Uniform noise
          cimg_for(*this,ptr,T) {
            double val = *ptr + nsigma*cimg::crand();
            if (val>max) val = max;
            if (val<min) val = min;
            *ptr = (T)val;
          }
        } break;
        case 2 : { // Salt & Pepper noise
          if (nsigma<0) nsigma = -nsigma;
          if (M==m) { m = 0; M = (float)(cimg::type<T>::is_float()?1:cimg::type<T>::max()); }
          cimg_for(*this,ptr,T) if (cimg::rand()*100<nsigma) *ptr = (T)(cimg::rand()<0.5?M:m);
        } break;

        case 3 : { // Poisson Noise
          cimg_for(*this,ptr,T) *ptr = (T)cimg::prand(*ptr);
        } break;

        case 4 : { // Rice noise
          const double sqrt2 = (double)cimg_std::sqrt(2.0);
          cimg_for(*this,ptr,T) {
            const double
              val0 = (double)*ptr/sqrt2,
              re = val0 + nsigma*cimg::grand(),
              im = val0 + nsigma*cimg::grand();
            double val = cimg_std::sqrt(re*re + im*im);
            if (val>max) val = max;
            if (val<min) val = min;
            *ptr = (T)val;
          }
        } break;
        default :
          throw CImgArgumentException("CImg<%s>::noise() : Invalid noise type %d "
                                      "(should be {0=Gaussian, 1=Uniform, 2=Salt&Pepper, 3=Poisson}).",pixel_type(),noise_type);
        }
      }
      return *this;
    }

    CImg<T> get_noise(const double sigma, const unsigned int noise_type=0) const {
      return (+*this).noise(sigma,noise_type);
    }

    //! Compute the result of the Deriche filter.
    /**
       The Canny-Deriche filter is a recursive algorithm allowing to compute blurred derivatives of
       order 0,1 or 2 of an image.
    **/
    CImg<T>& deriche(const float sigma, const int order=0, const char axis='x', const bool cond=true) {
#define _cimg_deriche2_apply \
  Tfloat *ptrY = Y.data, yb = 0, yp = 0; \
  T xp = (T)0; \
  if (cond) { xp = *ptrX; yb = yp = (Tfloat)(coefp*xp); } \
  for (int m=0; m<N; ++m) { \
    const T xc = *ptrX; ptrX+=off; \
    const Tfloat yc = *(ptrY++) = (Tfloat)(a0*xc + a1*xp - b1*yp - b2*yb); \
    xp = xc; yb = yp; yp = yc; \
  } \
  T xn = (T)0, xa = (T)0; \
  Tfloat yn = 0, ya = 0; \
  if (cond) { xn = xa = *(ptrX-off); yn = ya = (Tfloat)coefn*xn; } \
  for (int n=N-1; n>=0; --n) { \
    const T xc = *(ptrX-=off); \
    const Tfloat yc = (Tfloat)(a2*xn + a3*xa - b1*yn - b2*ya); \
    xa = xn; xn = xc; ya = yn; yn = yc; \
    *ptrX = (T)(*(--ptrY)+yc); \
  }
      if (sigma<0)
        throw CImgArgumentException("CImg<%s>::deriche() : Given filter variance (sigma = %g) is negative",
                                    pixel_type(),sigma);
      if (is_empty() || (sigma<0.1 && !order)) return *this;
      const float
        nsigma = sigma<0.1f?0.1f:sigma,
        alpha = 1.695f/nsigma,
        ema = (float)cimg_std::exp(-alpha),
        ema2 = (float)cimg_std::exp(-2*alpha),
        b1 = -2*ema,
        b2 = ema2;
      float a0 = 0, a1 = 0, a2 = 0, a3 = 0, coefp = 0, coefn = 0;
      switch (order) {
      case 0 : {
        const float k = (1-ema)*(1-ema)/(1+2*alpha*ema-ema2);
        a0 = k;
        a1 = k*(alpha-1)*ema;
        a2 = k*(alpha+1)*ema;
        a3 = -k*ema2;
      } break;
      case 1 : {
        const float k = (1-ema)*(1-ema)/ema;
        a0 = k*ema;
        a1 = a3 = 0;
        a2 = -a0;
      } break;
      case 2 : {
        const float
          ea = (float)cimg_std::exp(-alpha),
          k = -(ema2-1)/(2*alpha*ema),
          kn = (-2*(-1+3*ea-3*ea*ea+ea*ea*ea)/(3*ea+1+3*ea*ea+ea*ea*ea));
        a0 = kn;
        a1 = -kn*(1+k*alpha)*ema;
        a2 = kn*(1-k*alpha)*ema;
        a3 = -kn*ema2;
      } break;
      default :
        throw CImgArgumentException("CImg<%s>::deriche() : Given filter order (order = %u) must be 0,1 or 2",
                                    pixel_type(),order);
      }
      coefp = (a0+a1)/(1+b1+b2);
      coefn = (a2+a3)/(1+b1+b2);
      switch (cimg::uncase(axis)) {
      case 'x' : {
        const int N = width, off = 1;
        CImg<Tfloat> Y(N);
        cimg_forYZV(*this,y,z,v) { T *ptrX = ptr(0,y,z,v); _cimg_deriche2_apply; }
      } break;
      case 'y' : {
        const int N = height, off = width;
        CImg<Tfloat> Y(N);
        cimg_forXZV(*this,x,z,v) { T *ptrX = ptr(x,0,z,v); _cimg_deriche2_apply; }
      } break;
      case 'z' : {
        const int N = depth, off = width*height;
        CImg<Tfloat> Y(N);
        cimg_forXYV(*this,x,y,v) { T *ptrX = ptr(x,y,0,v); _cimg_deriche2_apply; }
      } break;
      case 'v' : {
        const int N = dim, off = width*height*depth;
        CImg<Tfloat> Y(N);
        cimg_forXYZ(*this,x,y,z) { T *ptrX = ptr(x,y,z,0); _cimg_deriche2_apply; }
      } break;
      }
      return *this;
    }

    CImg<Tfloat> get_deriche(const float sigma, const int order=0, const char axis='x', const bool cond=true) const {
      return CImg<Tfloat>(*this,false).deriche(sigma,order,axis,cond);
    }

    //! Return a blurred version of the image, using a Canny-Deriche filter.
    /**
       Blur the image with an anisotropic exponential filter (Deriche filter of order 0).
    **/
    CImg<T>& blur(const float sigmax, const float sigmay, const float sigmaz, const bool cond=true) {
      if (!is_empty()) {
        if (width>1  && sigmax>0) deriche(sigmax,0,'x',cond);
        if (height>1 && sigmay>0) deriche(sigmay,0,'y',cond);
        if (depth>1  && sigmaz>0) deriche(sigmaz,0,'z',cond);
      }
      return *this;
    }

    CImg<Tfloat> get_blur(const float sigmax, const float sigmay, const float sigmaz,
                          const bool cond=true) const {
      return CImg<Tfloat>(*this,false).blur(sigmax,sigmay,sigmaz,cond);
    }

    //! Return a blurred version of the image, using a Canny-Deriche filter.
    CImg<T>& blur(const float sigma, const bool cond=true) {
      return blur(sigma,sigma,sigma,cond);
    }

    CImg<Tfloat> get_blur(const float sigma, const bool cond=true) const {
      return CImg<Tfloat>(*this,false).blur(sigma,cond);
    }

    //! Blur the image anisotropically following a field of diffusion tensors.
    /**
       \param G = Field of square roots of diffusion tensors used to drive the smoothing.
       \param amplitude = amplitude of the smoothing.
       \param dl = spatial discretization.
       \param da = angular discretization.
       \param gauss_prec = precision of the gaussian function.
       \param interpolation Used interpolation scheme (0 = nearest-neighbor, 1 = linear, 2 = Runge-Kutta)
       \param fast_approx = Tell to use the fast approximation or not.
    **/
    template<typename t>
    CImg<T>& blur_anisotropic(const CImg<t>& G, const float amplitude=60, const float dl=0.8f, const float da=30,
                              const float gauss_prec=2, const unsigned int interpolation_type=0, const bool fast_approx=true) {
#define _cimg_valign2d(i,j) \
    { Tfloat &u = W(i,j,0,0), &v = W(i,j,0,1); \
    if (u*curru + v*currv<0) { u=-u; v=-v; }}
#define _cimg_valign3d(i,j,k) \
    { Tfloat &u = W(i,j,k,0), &v = W(i,j,k,1), &w = W(i,j,k,2); \
    if (u*curru + v*currv + w*currw<0) { u=-u; v=-v; w=-w; }}

      // Check arguments and init variables
      if (!is_empty() && amplitude>0) {
        if (!G || (G.dim!=3 && G.dim!=6) || G.width!=width || G.height!=height || G.depth!=depth)
          throw CImgArgumentException("CImg<%s>::blur_anisotropic() : Specified tensor field (%u,%u,%u,%u) is not valid.",
                                      pixel_type(),G.width,G.height,G.depth,G.dim);

        const float sqrt2amplitude = (float)cimg_std::sqrt(2*amplitude);
        const bool threed = (G.dim>=6);
        const int
          dx1 = dimx()-1,
          dy1 = dimy()-1,
          dz1 = dimz()-1;
        CImg<Tfloat>
          dest(width,height,depth,dim,0),
          W(width,height,depth,threed?4:3),
          tmp(dim);
        int N = 0;

        if (threed)
          // 3D version of the algorithm
          for (float phi=(180%(int)da)/2.0f; phi<=180; phi+=da) {
            const float
              phir = (float)(phi*cimg::valuePI/180),
              datmp = (float)(da/cimg_std::cos(phir)),
              da2 = datmp<1?360.0f:datmp;

            for (float theta=0; theta<360; (theta+=da2),++N) {
              const float
                thetar = (float)(theta*cimg::valuePI/180),
                vx = (float)(cimg_std::cos(thetar)*cimg_std::cos(phir)),
                vy = (float)(cimg_std::sin(thetar)*cimg_std::cos(phir)),
                vz = (float)cimg_std::sin(phir);
              const t
                *pa = G.ptr(0,0,0,0),
                *pb = G.ptr(0,0,0,1),
                *pc = G.ptr(0,0,0,2),
                *pd = G.ptr(0,0,0,3),
                *pe = G.ptr(0,0,0,4),
                *pf = G.ptr(0,0,0,5);
              Tfloat
                *pd0 = W.ptr(0,0,0,0),
                *pd1 = W.ptr(0,0,0,1),
                *pd2 = W.ptr(0,0,0,2),
                *pd3 = W.ptr(0,0,0,3);
              cimg_forXYZ(G,xg,yg,zg) {
                const t
                  a = *(pa++), b = *(pb++), c = *(pc++),
                  d = *(pd++), e = *(pe++), f = *(pf++);
                const float
                  u = (float)(a*vx + b*vy + c*vz),
                  v = (float)(b*vx + d*vy + e*vz),
                  w = (float)(c*vx + e*vy + f*vz),
                  n = (float)cimg_std::sqrt(1e-5+u*u+v*v+w*w),
                  dln = dl/n;
                *(pd0++) = (Tfloat)(u*dln);
                *(pd1++) = (Tfloat)(v*dln);
                *(pd2++) = (Tfloat)(w*dln);
                *(pd3++) = (Tfloat)n;
              }

              cimg_forXYZ(*this,x,y,z) {
                tmp.fill(0);
                const float
                  cu = (float)W(x,y,z,0),
                  cv = (float)W(x,y,z,1),
                  cw = (float)W(x,y,z,2),
                  n = (float)W(x,y,z,3),
                  fsigma = (float)(n*sqrt2amplitude),
                  length = gauss_prec*fsigma,
                  fsigma2 = 2*fsigma*fsigma;
                float
                  S = 0,
                  pu = cu,
                  pv = cv,
                  pw = cw,
                  X = (float)x,
                  Y = (float)y,
                  Z = (float)z;

                switch (interpolation_type) {
                case 0 : {
                  // Nearest neighbor
                  for (float l=0; l<length && X>=0 && X<=dx1 && Y>=0 && Y<=dy1 && Z>=0 && Z<=dz1; l+=dl) {
                    const int
                      cx = (int)(X+0.5f),
                      cy = (int)(Y+0.5f),
                      cz = (int)(Z+0.5f);
                    float
                      u = (float)W(cx,cy,cz,0),
                      v = (float)W(cx,cy,cz,1),
                      w = (float)W(cx,cy,cz,2);
                    if ((pu*u + pv*v + pw*w)<0) { u=-u; v=-v; w=-w; }
                    if (fast_approx) { cimg_forV(*this,k) tmp[k]+=(Tfloat)(*this)(cx,cy,cz,k); ++S; }
                    else {
                      const float coef = (float)cimg_std::exp(-l*l/fsigma2);
                      cimg_forV(*this,k) tmp[k]+=(Tfloat)(coef*(*this)(cx,cy,cz,k));
                      S+=coef;
                    }
                    X+=(pu=u); Y+=(pv=v); Z+=(pw=w);
                  }
                } break;

                case 1 : {
                  // Linear interpolation
                  for (float l=0; l<length && X>=0 && X<=dx1 && Y>=0 && Y<=dy1 && Z>=0 && Z<=dz1; l+=dl) {
                    const int
                      cx = (int)X, px = (cx-1<0)?0:cx-1, nx = (cx+1>dx1)?dx1:cx+1,
                      cy = (int)Y, py = (cy-1<0)?0:cy-1, ny = (cy+1>dy1)?dy1:cy+1,
                      cz = (int)Z, pz = (cz-1<0)?0:cz-1, nz = (cz+1>dz1)?dz1:cz+1;
                    const float
                      curru = (float)W(cx,cy,cz,0),
                      currv = (float)W(cx,cy,cz,1),
                      currw = (float)W(cx,cy,cz,2);
                    _cimg_valign3d(px,py,pz); _cimg_valign3d(cx,py,pz); _cimg_valign3d(nx,py,pz);
                    _cimg_valign3d(px,cy,pz); _cimg_valign3d(cx,cy,pz); _cimg_valign3d(nx,cy,pz);
                    _cimg_valign3d(px,ny,pz); _cimg_valign3d(cx,ny,pz); _cimg_valign3d(nx,ny,pz);
                    _cimg_valign3d(px,py,cz); _cimg_valign3d(cx,py,cz); _cimg_valign3d(nx,py,cz);
                    _cimg_valign3d(px,cy,cz);                           _cimg_valign3d(nx,cy,cz);
                    _cimg_valign3d(px,ny,cz); _cimg_valign3d(cx,ny,cz); _cimg_valign3d(nx,ny,cz);
                    _cimg_valign3d(px,py,nz); _cimg_valign3d(cx,py,nz); _cimg_valign3d(nx,py,nz);
                    _cimg_valign3d(px,cy,nz); _cimg_valign3d(cx,cy,nz); _cimg_valign3d(nx,cy,nz);
                    _cimg_valign3d(px,ny,nz); _cimg_valign3d(cx,ny,nz); _cimg_valign3d(nx,ny,nz);
                    float
                      u = (float)(W._linear_atXYZ(X,Y,Z,0)),
                      v = (float)(W._linear_atXYZ(X,Y,Z,1)),
                      w = (float)(W._linear_atXYZ(X,Y,Z,2));
                    if ((pu*u + pv*v + pw*w)<0) { u=-u; v=-v; w=-w; }
                    if (fast_approx) { cimg_forV(*this,k) tmp[k]+=(Tfloat)_linear_atXYZ(X,Y,Z,k); ++S; }
                    else {
                      const float coef = (float)cimg_std::exp(-l*l/fsigma2);
                      cimg_forV(*this,k) tmp[k]+=(Tfloat)(coef*_linear_atXYZ(X,Y,Z,k));
                      S+=coef;
                    }
                    X+=(pu=u); Y+=(pv=v); Z+=(pw=w);
                  }
                } break;

                default : {
                  // 2nd order Runge Kutta
                  for (float l=0; l<length && X>=0 && X<=dx1 && Y>=0 && Y<=dy1 && Z>=0 && Z<=dz1; l+=dl) {
                    const int
                      cx = (int)X, px = (cx-1<0)?0:cx-1, nx = (cx+1>dx1)?dx1:cx+1,
                      cy = (int)Y, py = (cy-1<0)?0:cy-1, ny = (cy+1>dy1)?dy1:cy+1,
                      cz = (int)Z, pz = (cz-1<0)?0:cz-1, nz = (cz+1>dz1)?dz1:cz+1;
                    const float
                      curru = (float)W(cx,cy,cz,0),
                      currv = (float)W(cx,cy,cz,1),
                      currw = (float)W(cx,cy,cz,2);
                    _cimg_valign3d(px,py,pz); _cimg_valign3d(cx,py,pz); _cimg_valign3d(nx,py,pz);
                    _cimg_valign3d(px,cy,pz); _cimg_valign3d(cx,cy,pz); _cimg_valign3d(nx,cy,pz);
                    _cimg_valign3d(px,ny,pz); _cimg_valign3d(cx,ny,pz); _cimg_valign3d(nx,ny,pz);
                    _cimg_valign3d(px,py,cz); _cimg_valign3d(cx,py,cz); _cimg_valign3d(nx,py,cz);
                    _cimg_valign3d(px,cy,cz);                           _cimg_valign3d(nx,cy,cz);
                    _cimg_valign3d(px,ny,cz); _cimg_valign3d(cx,ny,cz); _cimg_valign3d(nx,ny,cz);
                    _cimg_valign3d(px,py,nz); _cimg_valign3d(cx,py,nz); _cimg_valign3d(nx,py,nz);
                    _cimg_valign3d(px,cy,nz); _cimg_valign3d(cx,cy,nz); _cimg_valign3d(nx,cy,nz);
                    _cimg_valign3d(px,ny,nz); _cimg_valign3d(cx,ny,nz); _cimg_valign3d(nx,ny,nz);
                    const float
                      u0 = (float)(0.5f*W._linear_atXYZ(X,Y,Z,0)),
                      v0 = (float)(0.5f*W._linear_atXYZ(X,Y,Z,1)),
                      w0 = (float)(0.5f*W._linear_atXYZ(X,Y,Z,2));
                    float
                      u = (float)(W._linear_atXYZ(X+u0,Y+v0,Z+w0,0)),
                      v = (float)(W._linear_atXYZ(X+u0,Y+v0,Z+w0,1)),
                      w = (float)(W._linear_atXYZ(X+u0,Y+v0,Z+w0,2));
                    if ((pu*u + pv*v + pw*w)<0) { u=-u; v=-v; w=-w; }
                    if (fast_approx) { cimg_forV(*this,k) tmp[k]+=(Tfloat)_linear_atXYZ(X,Y,Z,k); ++S; }
                    else {
                      const float coef = (float)cimg_std::exp(-l*l/fsigma2);
                      cimg_forV(*this,k) tmp[k]+=(Tfloat)(coef*_linear_atXYZ(X,Y,Z,k));
                      S+=coef;
                    }
                    X+=(pu=u); Y+=(pv=v); Z+=(pw=w);
                  }
                } break;
                }
                if (S>0) cimg_forV(dest,k) dest(x,y,z,k)+=tmp[k]/S;
                else cimg_forV(dest,k) dest(x,y,z,k)+=(Tfloat)((*this)(x,y,z,k));
                cimg_plugin_greycstoration_count;
              }
            }
          } else
            // 2D version of the algorithm
            for (float theta=(360%(int)da)/2.0f; theta<360; (theta+=da),++N) {
              const float
                thetar = (float)(theta*cimg::valuePI/180),
                vx = (float)(cimg_std::cos(thetar)),
                vy = (float)(cimg_std::sin(thetar));
              const t
                *pa = G.ptr(0,0,0,0),
                *pb = G.ptr(0,0,0,1),
                *pc = G.ptr(0,0,0,2);
              Tfloat
                *pd0 = W.ptr(0,0,0,0),
                *pd1 = W.ptr(0,0,0,1),
                *pd2 = W.ptr(0,0,0,2);
              cimg_forXY(G,xg,yg) {
                const t a = *(pa++), b = *(pb++), c = *(pc++);
                const float
                  u = (float)(a*vx + b*vy),
                  v = (float)(b*vx + c*vy),
                  n = (float)cimg_std::sqrt(1e-5+u*u+v*v),
                  dln = dl/n;
                *(pd0++) = (Tfloat)(u*dln);
                *(pd1++) = (Tfloat)(v*dln);
                *(pd2++) = (Tfloat)n;
              }

              cimg_forXY(*this,x,y) {
                tmp.fill(0);
                const float
                  cu = (float)W(x,y,0,0),
                  cv = (float)W(x,y,0,1),
                  n = (float)W(x,y,0,2),
                  fsigma = (float)(n*sqrt2amplitude),
                  length = gauss_prec*fsigma,
                  fsigma2 = 2*fsigma*fsigma;
                float
                  S = 0,
                  pu = cu,
                  pv = cv,
                  X = (float)x,
                  Y = (float)y;

                switch (interpolation_type) {

                case 0 : {
                  // Nearest-neighbor interpolation for 2D images
                  for (float l=0; l<length && X>=0 && X<=dx1 && Y>=0 && Y<=dy1; l+=dl) {
                    const int
                      cx = (int)(X+0.5f),
                      cy = (int)(Y+0.5f);
                    float
                      u = (float)W(cx,cy,0,0),
                      v = (float)W(cx,cy,0,1);
                    if ((pu*u + pv*v)<0) { u=-u; v=-v; }
                    if (fast_approx) { cimg_forV(*this,k) tmp[k]+=(Tfloat)(*this)(cx,cy,0,k); ++S; }
                    else {
                      const float coef = (float)cimg_std::exp(-l*l/fsigma2);
                      cimg_forV(*this,k) tmp[k]+=(Tfloat)(coef*(*this)(cx,cy,0,k));
                      S+=coef;
                    }
                    X+=(pu=u); Y+=(pv=v);
                  }
                } break;

                case 1 : {
                  // Linear interpolation for 2D images
                  for (float l=0; l<length && X>=0 && X<=dx1 && Y>=0 && Y<=dy1; l+=dl) {
                    const int
                      cx = (int)X, px = (cx-1<0)?0:cx-1, nx = (cx+1>dx1)?dx1:cx+1,
                      cy = (int)Y, py = (cy-1<0)?0:cy-1, ny = (cy+1>dy1)?dy1:cy+1;
                    const float
                      curru = (float)W(cx,cy,0,0),
                      currv = (float)W(cx,cy,0,1);
                    _cimg_valign2d(px,py); _cimg_valign2d(cx,py); _cimg_valign2d(nx,py);
                    _cimg_valign2d(px,cy);                        _cimg_valign2d(nx,cy);
                    _cimg_valign2d(px,ny); _cimg_valign2d(cx,ny); _cimg_valign2d(nx,ny);
                    float
                      u = (float)(W._linear_atXY(X,Y,0,0)),
                      v = (float)(W._linear_atXY(X,Y,0,1));
                    if ((pu*u + pv*v)<0) { u=-u; v=-v; }
                    if (fast_approx) { cimg_forV(*this,k) tmp[k]+=(Tfloat)_linear_atXY(X,Y,0,k); ++S; }
                    else {
                      const float coef = (float)cimg_std::exp(-l*l/fsigma2);
                      cimg_forV(*this,k) tmp[k]+=(Tfloat)(coef*_linear_atXY(X,Y,0,k));
                      S+=coef;
                    }
                    X+=(pu=u); Y+=(pv=v);
                  }
                } break;

                default : {
                  // 2nd-order Runge-kutta interpolation for 2D images
                  for (float l=0; l<length && X>=0 && X<=dx1 && Y>=0 && Y<=dy1; l+=dl) {
                    const int
                      cx = (int)X, px = (cx-1<0)?0:cx-1, nx = (cx+1>dx1)?dx1:cx+1,
                      cy = (int)Y, py = (cy-1<0)?0:cy-1, ny = (cy+1>dy1)?dy1:cy+1;
                    const float
                      curru = (float)W(cx,cy,0,0),
                      currv = (float)W(cx,cy,0,1);
                    _cimg_valign2d(px,py); _cimg_valign2d(cx,py); _cimg_valign2d(nx,py);
                    _cimg_valign2d(px,cy);                        _cimg_valign2d(nx,cy);
                    _cimg_valign2d(px,ny); _cimg_valign2d(cx,ny); _cimg_valign2d(nx,ny);
                    const float
                      u0 = (float)(0.5f*W._linear_atXY(X,Y,0,0)),
                      v0 = (float)(0.5f*W._linear_atXY(X,Y,0,1));
                    float
                      u = (float)(W._linear_atXY(X+u0,Y+v0,0,0)),
                      v = (float)(W._linear_atXY(X+u0,Y+v0,0,1));
                    if ((pu*u + pv*v)<0) { u=-u; v=-v; }
                    if (fast_approx) { cimg_forV(*this,k) tmp[k]+=(Tfloat)_linear_atXY(X,Y,0,k); ++S; }
                    else {
                      const float coef = (float)cimg_std::exp(-l*l/fsigma2);
                      cimg_forV(*this,k) tmp[k]+=(Tfloat)(coef*_linear_atXY(X,Y,0,k));
                      S+=coef;
                    }
                    X+=(pu=u); Y+=(pv=v);
                  }
                }
                }
                if (S>0) cimg_forV(dest,k) dest(x,y,0,k)+=tmp[k]/S;
                else cimg_forV(dest,k) dest(x,y,0,k)+=(Tfloat)((*this)(x,y,0,k));
                cimg_plugin_greycstoration_count;
              }
            }
        const Tfloat *ptrs = dest.data+dest.size();
        const T m = cimg::type<T>::min(), M = cimg::type<T>::max();
        cimg_for(*this,ptrd,T) { const Tfloat val = *(--ptrs)/N; *ptrd = val<m?m:(val>M?M:(T)val); }
      }
      return *this;
    }

    template<typename t>
    CImg<T> get_blur_anisotropic(const CImg<t>& G, const float amplitude=60, const float dl=0.8f, const float da=30,
                                 const float gauss_prec=2, const unsigned int interpolation_type=0, const bool fast_approx=true) const {
      return (+*this).blur_anisotropic(G,amplitude,dl,da,gauss_prec,interpolation_type,fast_approx);
    }

    //! Blur an image in an anisotropic way.
    /**
       \param mask Binary mask.
       \param amplitude Amplitude of the anisotropic blur.
       \param sharpness Contour preservation.
       \param anisotropy Smoothing anisotropy.
       \param alpha Image pre-blurring (gaussian).
       \param sigma Regularity of the tensor-valued geometry.
       \param dl Spatial discretization.
       \param da Angular discretization.
       \param gauss_prec Precision of the gaussian function.
       \param interpolation_type Used interpolation scheme (0 = nearest-neighbor, 1 = linear, 2 = Runge-Kutta)
       \param fast_approx Tell to use the fast approximation or not
       \param geom_factor Geometry factor.
    **/
    template<typename tm>
    CImg<T>& blur_anisotropic(const CImg<tm>& mask, const float amplitude, const float sharpness=0.7f, const float anisotropy=0.3f,
                              const float alpha=0.6f, const float sigma=1.1f, const float dl=0.8f, const float da=30,
                              const float gauss_prec=2, const unsigned int interpolation_type=0, const bool fast_approx=true,
                              const float geom_factor=1) {
      if (!is_empty() && amplitude>0) {
        if (amplitude==0) return *this;
        if (amplitude<0 || sharpness<0 || anisotropy<0 || anisotropy>1 || alpha<0 || sigma<0 || dl<0 || da<0 || gauss_prec<0)
          throw CImgArgumentException("CImg<%s>::blur_anisotropic() : Given parameters are amplitude(%g), sharpness(%g), "
                                      "anisotropy(%g), alpha(%g), sigma(%g), dl(%g), da(%g), gauss_prec(%g).\n"
                                      "Admissible parameters are in the range : amplitude>0, sharpness>0, anisotropy in [0,1], "
                                      "alpha>0, sigma>0, dl>0, da>0, gauss_prec>0.",
                                      pixel_type(),amplitude,sharpness,anisotropy,alpha,sigma,dl,da,gauss_prec);
        const bool threed = (depth>1), no_mask = mask.is_empty();
        const float nsharpness = cimg::max(sharpness,1e-5f), power1 = 0.5f*nsharpness, power2 = power1/(1e-7f+1-anisotropy);
        CImg<floatT> blurred = CImg<floatT>(*this,false).blur(alpha);
        if (geom_factor>0) blurred*=geom_factor;
        else blurred.normalize(0,-geom_factor);

        if (threed) { // Field for 3D volumes
          cimg_plugin_greycstoration_lock;
          CImg<floatT> val(3), vec(3,3), G(blurred.get_structure_tensor());
          if (sigma>0) G.blur(sigma);
          cimg_forXYZ(*this,x,y,z) {
            if (no_mask || mask(x,y,z)) {
              G.get_tensor_at(x,y,z).symmetric_eigen(val,vec);
              const float l1 = val[2], l2 = val[1], l3 = val[0],
                ux = vec(0,0), uy = vec(0,1), uz = vec(0,2),
                vx = vec(1,0), vy = vec(1,1), vz = vec(1,2),
                wx = vec(2,0), wy = vec(2,1), wz = vec(2,2),
                n1 = (float)cimg_std::pow(1+l1+l2+l3,-power1),
                n2 = (float)cimg_std::pow(1+l1+l2+l3,-power2);
              G(x,y,z,0) = n1*(ux*ux + vx*vx) + n2*wx*wx;
              G(x,y,z,1) = n1*(ux*uy + vx*vy) + n2*wx*wy;
              G(x,y,z,2) = n1*(ux*uz + vx*vz) + n2*wx*wz;
              G(x,y,z,3) = n1*(uy*uy + vy*vy) + n2*wy*wy;
              G(x,y,z,4) = n1*(uy*uz + vy*vz) + n2*wy*wz;
              G(x,y,z,5) = n1*(uz*uz + vz*vz) + n2*wz*wz;
            } else G(x,y,z,0) = G(x,y,z,1) = G(x,y,z,2) = G(x,y,z,3) = G(x,y,z,4) = G(x,y,z,5) = 0;
            cimg_plugin_greycstoration_count;
          }
          cimg_plugin_greycstoration_unlock;
          blur_anisotropic(G,amplitude,dl,da,gauss_prec,interpolation_type,fast_approx);
        } else { // Field for 2D images
          cimg_plugin_greycstoration_lock;
          CImg<floatT> val(2), vec(2,2), G(blurred.get_structure_tensor());
          if (sigma>0) G.blur(sigma);
          cimg_forXY(*this,x,y) {
            if (no_mask || mask(x,y)) {
              G.get_tensor_at(x,y).symmetric_eigen(val,vec);
              const float l1 = val[1], l2 = val[0],
                ux = vec(1,0), uy = vec(1,1),
                vx = vec(0,0), vy = vec(0,1),
                n1 = (float)cimg_std::pow(1+l1+l2,-power1),
                n2 = (float)cimg_std::pow(1+l1+l2,-power2);
              G(x,y,0,0) = n1*ux*ux + n2*vx*vx;
              G(x,y,0,1) = n1*ux*uy + n2*vx*vy;
              G(x,y,0,2) = n1*uy*uy + n2*vy*vy;
            } else G(x,y,0,0) = G(x,y,0,1) = G(x,y,0,2) = 0;
            cimg_plugin_greycstoration_count;
          }
          cimg_plugin_greycstoration_unlock;
          blur_anisotropic(G,amplitude,dl,da,gauss_prec,interpolation_type,fast_approx);
        }
      }
      return *this;
    }

    template<typename tm>
    CImg<T> get_blur_anisotropic(const CImg<tm>& mask, const float amplitude, const float sharpness=0.7f, const float anisotropy=0.3f,
                                 const float alpha=0.6f, const float sigma=1.1f, const float dl=0.8f,
                                 const float da=30, const float gauss_prec=2, const unsigned int interpolation_type=0,
                                 const bool fast_approx=true, const float geom_factor=1) const {
      return (+*this).blur_anisotropic(mask,amplitude,sharpness,anisotropy,alpha,sigma,dl,da,gauss_prec,interpolation_type,fast_approx,geom_factor);
    }

    //! Blur an image following in an anisotropic way.
    CImg<T>& blur_anisotropic(const float amplitude, const float sharpness=0.7f, const float anisotropy=0.3f,
                              const float alpha=0.6f, const float sigma=1.1f, const float dl=0.8f, const float da=30,
                              const float gauss_prec=2, const unsigned int interpolation_type=0, const bool fast_approx=true,
                              const float geom_factor=1) {
      return blur_anisotropic(CImg<T>(),amplitude,sharpness,anisotropy,alpha,sigma,dl,da,gauss_prec,interpolation_type,fast_approx,geom_factor);
    }

    CImg<T> get_blur_anisotropic(const float amplitude, const float sharpness=0.7f, const float anisotropy=0.3f,
                                 const float alpha=0.6f, const float sigma=1.1f, const float dl=0.8f,
                                 const float da=30, const float gauss_prec=2, const unsigned int interpolation_type=0,
                                 const bool fast_approx=true, const float geom_factor=1) const {
      return (+*this).blur_anisotropic(amplitude,sharpness,anisotropy,alpha,sigma,dl,da,gauss_prec,interpolation_type,fast_approx,geom_factor);
    }

    //! Blur an image using the bilateral filter.
    /**
       \param sigmax Amount of blur along the X-axis.
       \param sigmay Amount of blur along the Y-axis.
       \param sigmaz Amount of blur along the Z-axis.
       \param sigmar Amount of blur along the range axis.
       \param bgridx Size of the bilateral grid along the X-axis.
       \param bgridy Size of the bilateral grid along the Y-axis.
       \param bgridz Size of the bilateral grid along the Z-axis.
       \param bgridr Size of the bilateral grid along the range axis.
       \param interpolation_type Use interpolation for image slicing.
       \note This algorithm uses the optimisation technique proposed by S. Paris and F. Durand, in ECCV'2006
       (extended for 3D volumetric images).
    **/
    CImg<T>& blur_bilateral(const float sigmax, const float sigmay, const float sigmaz, const float sigmar,
                            const int bgridx, const int bgridy, const int bgridz, const int bgridr,
                            const bool interpolation_type=true) {
      T m, M = maxmin(m);
      const float range = (float)(1.0f+M-m);
      const unsigned int
        bx0 = bgridx>=0?bgridx:width*(-bgridx)/100,
        by0 = bgridy>=0?bgridy:height*(-bgridy)/100,
        bz0 = bgridz>=0?bgridz:depth*(-bgridz)/100,
        br0 = bgridr>=0?bgridr:(int)(-range*bgridr/100),
        bx = bx0>0?bx0:1,
        by = by0>0?by0:1,
        bz = bz0>0?bz0:1,
        br = br0>0?br0:1;
      const float
        nsigmax = sigmax*bx/width,
        nsigmay = sigmay*by/height,
        nsigmaz = sigmaz*bz/depth,
        nsigmar = sigmar*br/range;
      if (nsigmax>0 || nsigmay>0 || nsigmaz>0 || nsigmar>0) {
        const bool threed = depth>1;
        if (threed) { // 3d version of the algorithm
          CImg<floatT> bgrid(bx,by,bz,br), bgridw(bx,by,bz,br);
          cimg_forV(*this,k) {
            bgrid.fill(0); bgridw.fill(0);
            cimg_forXYZ(*this,x,y,z) {
              const T val = (*this)(x,y,z,k);
              const int X = x*bx/width, Y = y*by/height, Z = z*bz/depth, R = (int)((val-m)*br/range);
              bgrid(X,Y,Z,R) = (float)val;
              bgridw(X,Y,Z,R) = 1;
            }
            bgrid.blur(nsigmax,nsigmay,nsigmaz,true).deriche(nsigmar,0,'v',false);
            bgridw.blur(nsigmax,nsigmay,nsigmaz,true).deriche(nsigmar,0,'v',false);
            if (interpolation_type) cimg_forXYZ(*this,x,y,z) {
              const T val = (*this)(x,y,z,k);
              const float X = (float)x*bx/width, Y = (float)y*by/height, Z = (float)z*bz/depth, R = (float)((val-m)*br/range),
                bval0 = bgrid._linear_atXYZV(X,Y,Z,R), bval1 = bgridw._linear_atXYZV(X,Y,Z,R);
              (*this)(x,y,z,k) = (T)(bval0/bval1);
            } else cimg_forXYZ(*this,x,y,z) {
              const T val = (*this)(x,y,z,k);
              const int X = x*bx/width, Y = y*by/height, Z = z*bz/depth, R = (int)((val-m)*br/range);
              const float bval0 = bgrid(X,Y,Z,R), bval1 = bgridw(X,Y,Z,R);
              (*this)(x,y,z,k) = (T)(bval0/bval1);
            }
          }
        } else { // 2d version of the algorithm
          CImg<floatT> bgrid(bx,by,br,2);
          cimg_forV(*this,k) {
            bgrid.fill(0);
            cimg_forXY(*this,x,y) {
              const T val = (*this)(x,y,k);
              const int X = x*bx/width, Y = y*by/height, R = (int)((val-m)*br/range);
              bgrid(X,Y,R,0) = (float)val;
              bgrid(X,Y,R,1) = 1;
            }
            bgrid.blur(nsigmax,nsigmay,0,true).blur(0,0,nsigmar,false);
            if (interpolation_type) cimg_forXY(*this,x,y) {
              const T val = (*this)(x,y,k);
              const float X = (float)x*bx/width, Y = (float)y*by/height, R = (float)((val-m)*br/range),
                bval0 = bgrid._linear_atXYZ(X,Y,R,0), bval1 = bgrid._linear_atXYZ(X,Y,R,1);
              (*this)(x,y,k) = (T)(bval0/bval1);
            } else cimg_forXY(*this,x,y) {
              const T val = (*this)(x,y,k);
              const int X = x*bx/width, Y = y*by/height, R = (int)((val-m)*br/range);
              const float bval0 = bgrid(X,Y,R,0), bval1 = bgrid(X,Y,R,1);
              (*this)(x,y,k) = (T)(bval0/bval1);
            }
          }
        }
      }
      return *this;
    }

    CImg<T> get_blur_bilateral(const float sigmax, const float sigmay, const float sigmaz, const float sigmar,
                               const int bgridx, const int bgridy, const int bgridz, const int bgridr,
                               const bool interpolation_type=true) const {
      return (+*this).blur_bilateral(sigmax,sigmay,sigmaz,sigmar,bgridx,bgridy,bgridz,bgridr,interpolation_type);
    }

    //! Blur an image using the bilateral filter.
    CImg<T>& blur_bilateral(const float sigmas, const float sigmar, const int bgrids=-33, const int bgridr=32,
                            const bool interpolation_type=true) {
      return blur_bilateral(sigmas,sigmas,sigmas,sigmar,bgrids,bgrids,bgrids,bgridr,interpolation_type);
    }

    CImg<T> get_blur_bilateral(const float sigmas, const float sigmar, const int bgrids=-33, const int bgridr=32,
                               const bool interpolation_type=true) const {
      return (+*this).blur_bilateral(sigmas,sigmas,sigmas,sigmar,bgrids,bgrids,bgrids,bgridr,interpolation_type);
    }

    //! Blur an image in its patch-based space.
    CImg<T>& blur_patch(const unsigned int patch_size, const float sigma_p, const float sigma_s=10,
                        const unsigned int lookup_size=4, const bool fast_approx=true) {

#define _cimg_blur_patch_fastfunc(x) ((x)>3?0:1)
#define _cimg_blur_patch_slowfunc(x) cimg_std::exp(-(x))
#define _cimg_blur_patch3d(N,func) { \
  const unsigned int N3 = N*N*N; \
  cimg_for##N##XYZ(*this,x,y,z) { \
    cimg_plugin_greycstoration_count; \
    cimg_forV(*this,k) cimg_get##N##x##N##x##N(*this,x,y,z,k,P.ptr(N3*k)); \
    const int x0 = x - rsize1, y0 = y - rsize1, z0 = z - rsize1, x1 = x + rsize2, y1 = y + rsize2, z1 = z + rsize2; \
    float sum_weights = 0; \
    cimg_for_in##N##XYZ(*this,x0,y0,z0,x1,y1,z1,p,q,r) { \
      cimg_forV(*this,k) cimg_get##N##x##N##x##N(*this,p,q,r,k,Q.ptr(N3*k)); \
      float distance2 = 0; \
      const T *pQ = Q.end(); \
      cimg_for(P,pP,T) { const float dI = (float)*pP - (float)*(--pQ); distance2+=dI*dI; } \
      distance2/=Pnorm; \
      const float dx = (float)p - x, dy = (float)q - y, dz = (float)r - z, \
        alldist = distance2 + (dx*dx + dy*dy + dz*dz)/sigma_s2, weight = (float)func(alldist); \
      sum_weights+=weight; \
      { cimg_forV(*this,k) res(x,y,z,k)+=weight*(*this)(p,q,r,k); } \
    } \
    if (sum_weights>0) cimg_forV(*this,k) res(x,y,z,k)/=sum_weights; else cimg_forV(*this,k) res(x,y,z,k) = (Tfloat)((*this)(x,y,z,k)); \
  }}
#define _cimg_blur_patch2d(N,func) { \
  const unsigned int N2 = N*N; \
  cimg_for##N##XY(*this,x,y) { \
    cimg_plugin_greycstoration_count; \
    cimg_forV(*this,k) cimg_get##N##x##N(*this,x,y,0,k,P.ptr(N2*k)); \
    const int x0 = x-rsize1, y0 = y-rsize1, x1 = x+rsize2, y1 = y+rsize2; \
    float sum_weights = 0; \
    cimg_for_in##N##XY(*this,x0,y0,x1,y1,p,q) { \
      cimg_forV(*this,k) cimg_get##N##x##N(*this,p,q,0,k,Q.ptr(N2*k)); \
      float distance2 = 0; \
      const T *pQ = Q.end(); \
      cimg_for(P,pP,T) { const float dI = (float)*pP-(float)*(--pQ); distance2+=dI*dI; } \
      distance2/=Pnorm; \
      const float dx = (float)p-x, dy = (float)q-y, \
        alldist = distance2 + (dx*dx+dy*dy)/sigma_s2, weight = (float)func(alldist); \
      sum_weights+=weight; \
      { cimg_forV(*this,k) res(x,y,k)+=weight*(*this)(p,q,k); } \
    } \
    if (sum_weights>0) cimg_forV(*this,k) res(x,y,k)/=sum_weights; else cimg_forV(*this,k) res(x,y,k) = (Tfloat)((*this)(x,y,k)); \
  }}

      CImg<Tfloat> res(width,height,depth,dim,0);
      CImg<T> P(patch_size*patch_size*dim), Q(P);
      const float sigma_s2 = sigma_s*sigma_s, sigma_p2 = sigma_p*sigma_p, Pnorm = P.size()*sigma_p2;
      const int rsize2 = (int)lookup_size/2, rsize1 = rsize2-1+(lookup_size%2);
      if (depth>1) switch (patch_size) { // 3D version
      case 2 :
        if (fast_approx) { _cimg_blur_patch3d(2,_cimg_blur_patch_fastfunc); }
        else { _cimg_blur_patch3d(2,_cimg_blur_patch_slowfunc); }
        break;
      case 3 :
        if (fast_approx) { _cimg_blur_patch3d(3,_cimg_blur_patch_fastfunc); }
        else { _cimg_blur_patch3d(3,_cimg_blur_patch_slowfunc); }
        break;
      default : {
        const int psize1 = (int)patch_size/2, psize0 = psize1-1+(patch_size%2);
        cimg_forXYZ(*this,x,y,z) {
          cimg_plugin_greycstoration_count;
          P = get_crop(x - psize0,y - psize0,z - psize0,x + psize1,y + psize1,z + psize1,true);
          const int x0 = x - rsize1, y0 = y - rsize1, z0 = z - rsize1, x1 = x + rsize2, y1 = y + rsize2, z1 = z + rsize2;
          float sum_weights = 0;
          cimg_for_inXYZ(*this,x0,y0,z0,x1,y1,z1,p,q,r) {
            (Q = get_crop(p - psize0,q - psize0,r - psize0,p + psize1,q + psize1,r + psize1,true))-=P;
            const float
              dx = (float)x - p, dy = (float)y - q, dz = (float)z - r,
              distance2 = (float)(Q.pow(2).sum()/Pnorm + (dx*dx + dy*dy + dz*dz)/sigma_s2),
              weight = (float)cimg_std::exp(-distance2);
            sum_weights+=weight;
            cimg_forV(*this,k) res(x,y,z,k)+=weight*(*this)(p,q,r,k);
          }
          if (sum_weights>0) cimg_forV(*this,k) res(x,y,z,k)/=sum_weights; else cimg_forV(*this,k) res(x,y,z,k) = (Tfloat)((*this)(x,y,z,k));
        }
      }
      } else switch (patch_size) { // 2D version
      case 2 :
        if (fast_approx) { _cimg_blur_patch2d(2,_cimg_blur_patch_fastfunc); }
        else { _cimg_blur_patch2d(2,_cimg_blur_patch_slowfunc); }
        break;
      case 3 :
        if (fast_approx) { _cimg_blur_patch2d(3,_cimg_blur_patch_fastfunc); }
        else { _cimg_blur_patch2d(3,_cimg_blur_patch_slowfunc); }
        break;
      case 4 :
        if (fast_approx) { _cimg_blur_patch2d(4,_cimg_blur_patch_fastfunc); }
        else { _cimg_blur_patch2d(4,_cimg_blur_patch_slowfunc); }
        break;
      case 5 :
        if (fast_approx) { _cimg_blur_patch2d(5,_cimg_blur_patch_fastfunc); }
        else { _cimg_blur_patch2d(5,_cimg_blur_patch_slowfunc); }
        break;
      case 6 :
        if (fast_approx) { _cimg_blur_patch2d(6,_cimg_blur_patch_fastfunc); }
        else { _cimg_blur_patch2d(6,_cimg_blur_patch_slowfunc); }
        break;
      case 7 :
        if (fast_approx) { _cimg_blur_patch2d(7,_cimg_blur_patch_fastfunc); }
        else { _cimg_blur_patch2d(7,_cimg_blur_patch_slowfunc); }
        break;
      case 8 :
        if (fast_approx) { _cimg_blur_patch2d(8,_cimg_blur_patch_fastfunc); }
        else { _cimg_blur_patch2d(8,_cimg_blur_patch_slowfunc); }
        break;
      case 9 :
        if (fast_approx) { _cimg_blur_patch2d(9,_cimg_blur_patch_fastfunc); }
        else { _cimg_blur_patch2d(9,_cimg_blur_patch_slowfunc); }
        break;
      default : {
        const int psize1 = (int)patch_size/2, psize0 = psize1-1+(patch_size%2);
        cimg_forXY(*this,x,y) {
          cimg_plugin_greycstoration_count;
          P = get_crop(x - psize0,y - psize0,x + psize1,y + psize1,true);
          const int x0 = x - rsize1, y0 = y - rsize1, x1 = x + rsize2, y1 = y + rsize2;
          float sum_weights = 0;
          cimg_for_inXY(*this,x0,y0,x1,y1,p,q) {
            (Q = get_crop(p - psize0,q - psize0,p + psize1,q + psize1,true))-=P;
            const float
              dx = (float)x - p, dy = (float)y - q,
              distance2 = (float)(Q.pow(2).sum()/Pnorm + (dx*dx + dy*dy)/sigma_s2),
              weight = (float)cimg_std::exp(-distance2);
            sum_weights+=weight;
            cimg_forV(*this,k) res(x,y,0,k)+=weight*(*this)(p,q,0,k);
          }
          if (sum_weights>0) cimg_forV(*this,k) res(x,y,0,k)/=sum_weights; else cimg_forV(*this,k) res(x,y,0,k) = (Tfloat)((*this)(x,y,0,k));
        }
      }
      }
      return res.transfer_to(*this);
    }

    CImg<T> get_blur_patch(const unsigned int patch_size, const float sigma_p, const float sigma_s=10,
                           const unsigned int lookup_size=4, const bool fast_approx=true) const {
      return (+*this).blur_patch(patch_size,sigma_p,sigma_s,lookup_size,fast_approx);
    }

    //! Compute the Fast Fourier Transform of an image (along a specified axis).
    CImgList<Tfloat> get_FFT(const char axis, const bool invert=false) const {
      return CImgList<Tfloat>(*this).FFT(axis,invert);
    }

    //! Compute the Fast Fourier Transform on an image.
    CImgList<Tfloat> get_FFT(const bool invert=false) const {
      return CImgList<Tfloat>(*this).FFT(invert);
    }

    //! Apply a median filter.
    CImg<T>& blur_median(const unsigned int n) {
      return get_blur_median(n).transfer_to(*this);
    }

    CImg<T> get_blur_median(const unsigned int n) {
      CImg<T> res(width,height,depth,dim);
      if (!n || n==1) return *this;
      const int hl=n/2, hr=hl-1+n%2;
      if (res.depth!=1) {  // 3D median filter
        CImg<T> vois;
        cimg_forXYZV(*this,x,y,z,k) {
          const int
            x0 = x - hl, y0 = y - hl, z0 = z-hl, x1 = x + hr, y1 = y + hr, z1 = z+hr,
            nx0 = x0<0?0:x0, ny0 = y0<0?0:y0, nz0 = z0<0?0:z0,
            nx1 = x1>=dimx()?dimx()-1:x1, ny1 = y1>=dimy()?dimy()-1:y1, nz1 = z1>=dimz()?dimz()-1:z1;
          vois = get_crop(nx0,ny0,nz0,k,nx1,ny1,nz1,k);
          res(x,y,z,k) = vois.median();
        }
      } else {
#define _cimg_median_sort(a,b) if ((a)>(b)) cimg::swap(a,b)
        if (res.height!=1) switch (n) { // 2D median filter
        case 3 : {
          T I[9] = { 0 };
          CImg_3x3(J,T);
          cimg_forV(*this,k) cimg_for3x3(*this,x,y,0,k,I) {
            cimg_std::memcpy(J,I,9*sizeof(T));
            _cimg_median_sort(Jcp, Jnp); _cimg_median_sort(Jcc, Jnc); _cimg_median_sort(Jcn, Jnn);
            _cimg_median_sort(Jpp, Jcp); _cimg_median_sort(Jpc, Jcc); _cimg_median_sort(Jpn, Jcn);
            _cimg_median_sort(Jcp, Jnp); _cimg_median_sort(Jcc, Jnc); _cimg_median_sort(Jcn, Jnn);
            _cimg_median_sort(Jpp, Jpc); _cimg_median_sort(Jnc, Jnn); _cimg_median_sort(Jcc, Jcn);
            _cimg_median_sort(Jpc, Jpn); _cimg_median_sort(Jcp, Jcc); _cimg_median_sort(Jnp, Jnc);
            _cimg_median_sort(Jcc, Jcn); _cimg_median_sort(Jcc, Jnp); _cimg_median_sort(Jpn, Jcc);
            _cimg_median_sort(Jcc, Jnp);
            res(x,y,0,k) = Jcc;
          }
        } break;
        case 5 : {
          T I[25] = { 0 };
          CImg_5x5(J,T);
          cimg_forV(*this,k) cimg_for5x5(*this,x,y,0,k,I) {
            cimg_std::memcpy(J,I,25*sizeof(T));
            _cimg_median_sort(Jbb, Jpb); _cimg_median_sort(Jnb, Jab); _cimg_median_sort(Jcb, Jab); _cimg_median_sort(Jcb, Jnb);
            _cimg_median_sort(Jpp, Jcp); _cimg_median_sort(Jbp, Jcp); _cimg_median_sort(Jbp, Jpp); _cimg_median_sort(Jap, Jbc);
            _cimg_median_sort(Jnp, Jbc); _cimg_median_sort(Jnp, Jap); _cimg_median_sort(Jcc, Jnc); _cimg_median_sort(Jpc, Jnc);
            _cimg_median_sort(Jpc, Jcc); _cimg_median_sort(Jbn, Jpn); _cimg_median_sort(Jac, Jpn); _cimg_median_sort(Jac, Jbn);
            _cimg_median_sort(Jnn, Jan); _cimg_median_sort(Jcn, Jan); _cimg_median_sort(Jcn, Jnn); _cimg_median_sort(Jpa, Jca);
            _cimg_median_sort(Jba, Jca); _cimg_median_sort(Jba, Jpa); _cimg_median_sort(Jna, Jaa); _cimg_median_sort(Jcb, Jbp);
            _cimg_median_sort(Jnb, Jpp); _cimg_median_sort(Jbb, Jpp); _cimg_median_sort(Jbb, Jnb); _cimg_median_sort(Jab, Jcp);
            _cimg_median_sort(Jpb, Jcp); _cimg_median_sort(Jpb, Jab); _cimg_median_sort(Jpc, Jac); _cimg_median_sort(Jnp, Jac);
            _cimg_median_sort(Jnp, Jpc); _cimg_median_sort(Jcc, Jbn); _cimg_median_sort(Jap, Jbn); _cimg_median_sort(Jap, Jcc);
            _cimg_median_sort(Jnc, Jpn); _cimg_median_sort(Jbc, Jpn); _cimg_median_sort(Jbc, Jnc); _cimg_median_sort(Jba, Jna);
            _cimg_median_sort(Jcn, Jna); _cimg_median_sort(Jcn, Jba); _cimg_median_sort(Jpa, Jaa); _cimg_median_sort(Jnn, Jaa);
            _cimg_median_sort(Jnn, Jpa); _cimg_median_sort(Jan, Jca); _cimg_median_sort(Jnp, Jcn); _cimg_median_sort(Jap, Jnn);
            _cimg_median_sort(Jbb, Jnn); _cimg_median_sort(Jbb, Jap); _cimg_median_sort(Jbc, Jan); _cimg_median_sort(Jpb, Jan);
            _cimg_median_sort(Jpb, Jbc); _cimg_median_sort(Jpc, Jba); _cimg_median_sort(Jcb, Jba); _cimg_median_sort(Jcb, Jpc);
            _cimg_median_sort(Jcc, Jpa); _cimg_median_sort(Jnb, Jpa); _cimg_median_sort(Jnb, Jcc); _cimg_median_sort(Jnc, Jca);
            _cimg_median_sort(Jab, Jca); _cimg_median_sort(Jab, Jnc); _cimg_median_sort(Jac, Jna); _cimg_median_sort(Jbp, Jna);
            _cimg_median_sort(Jbp, Jac); _cimg_median_sort(Jbn, Jaa); _cimg_median_sort(Jpp, Jaa); _cimg_median_sort(Jpp, Jbn);
            _cimg_median_sort(Jcp, Jpn); _cimg_median_sort(Jcp, Jan); _cimg_median_sort(Jnc, Jpa); _cimg_median_sort(Jbn, Jna);
            _cimg_median_sort(Jcp, Jnc); _cimg_median_sort(Jcp, Jbn); _cimg_median_sort(Jpb, Jap); _cimg_median_sort(Jnb, Jpc);
            _cimg_median_sort(Jbp, Jcn); _cimg_median_sort(Jpc, Jcn); _cimg_median_sort(Jap, Jcn); _cimg_median_sort(Jab, Jbc);
            _cimg_median_sort(Jpp, Jcc); _cimg_median_sort(Jcp, Jac); _cimg_median_sort(Jab, Jpp); _cimg_median_sort(Jab, Jcp);
            _cimg_median_sort(Jcc, Jac); _cimg_median_sort(Jbc, Jac); _cimg_median_sort(Jpp, Jcp); _cimg_median_sort(Jbc, Jcc);
            _cimg_median_sort(Jpp, Jbc); _cimg_median_sort(Jpp, Jcn); _cimg_median_sort(Jcc, Jcn); _cimg_median_sort(Jcp, Jcn);
            _cimg_median_sort(Jcp, Jbc); _cimg_median_sort(Jcc, Jnn); _cimg_median_sort(Jcp, Jcc); _cimg_median_sort(Jbc, Jnn);
            _cimg_median_sort(Jcc, Jba); _cimg_median_sort(Jbc, Jba); _cimg_median_sort(Jbc, Jcc);
            res(x,y,0,k) = Jcc;
          }
        } break;
        default : {
          CImg<T> vois;
          cimg_forXYV(*this,x,y,k) {
            const int
              x0 = x - hl, y0 = y - hl, x1 = x + hr, y1 = y + hr,
              nx0 = x0<0?0:x0, ny0 = y0<0?0:y0,
              nx1 = x1>=dimx()?dimx()-1:x1, ny1 = y1>=dimy()?dimy()-1:y1;
            vois = get_crop(nx0,ny0,0,k,nx1,ny1,0,k);
            res(x,y,0,k) = vois.median();
          }
        }
        } else switch (n) { // 1D median filter
        case 2 : {
          T I[4] = { 0 };
          cimg_forV(*this,k) cimg_for2x2(*this,x,y,0,k,I) res(x,0,0,k) = (T)(0.5f*(I[0]+I[1]));
        } break;
        case 3 : {
          T I[9] = { 0 };
          cimg_forV(*this,k) cimg_for3x3(*this,x,y,0,k,I) {
            res(x,0,0,k) = I[3]<I[4]?
              (I[4]<I[5]?I[4]:
               (I[3]<I[5]?I[5]:I[3])):
              (I[3]<I[5]?I[3]:
               (I[4]<I[5]?I[5]:I[4]));
          }
        } break;
        default : {
          CImg<T> vois;
          cimg_forXV(*this,x,k) {
            const int
              x0 = x - hl, x1 = x + hr,
              nx0 = x0<0?0:x0, nx1 = x1>=dimx()?dimx()-1:x1;
            vois = get_crop(nx0,0,0,k,nx1,0,0,k);
            res(x,0,0,k) = vois.median();
          }
        }
        }
      }
      return res;
    }

    //! Sharpen image using anisotropic shock filters or inverse diffusion.
    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 valm, valM = maxmin(valm);
      const bool threed = (depth>1);
      const float nedge = 0.5f*edge;
      CImg<Tfloat> val, vec, veloc(width,height,depth,dim);

      if (threed) {
        CImg_3x3x3(I,T);
        if (sharpen_type) { // 3D Shock filter.
          CImg<Tfloat> G = (alpha>0?get_blur(alpha).get_structure_tensor():get_structure_tensor());
          if (sigma>0) G.blur(sigma);

          cimg_forXYZ(G,x,y,z) {
            G.get_tensor_at(x,y,z).symmetric_eigen(val,vec);
            G(x,y,z,0) = vec(0,0);
            G(x,y,z,1) = vec(0,1);
            G(x,y,z,2) = vec(0,2);
            G(x,y,z,3) = 1 - (Tfloat)cimg_std::pow(1+val[0]+val[1]+val[2],-(Tfloat)nedge);
          }
          cimg_forV(*this,k) cimg_for3x3x3(*this,x,y,z,k,I) {
            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 = (Tfloat)Incc + Ipcc - 2*Iccc,
              ixy = 0.25f*((Tfloat)Innc + Ippc - Inpc - Ipnc),
              ixz = 0.25f*((Tfloat)Incn + Ipcp - Incp - Ipcn),
              iyy = (Tfloat)Icnc + Icpc - 2*Iccc,
              iyz = 0.25f*((Tfloat)Icnn + Icpp - Icnp - Icpn),
              izz = (Tfloat)Iccn + Iccp - 2*Iccc,
              ixf = (Tfloat)Incc - Iccc,
              ixb = (Tfloat)Iccc - Ipcc,
              iyf = (Tfloat)Icnc - Iccc,
              iyb = (Tfloat)Iccc - Icpc,
              izf = (Tfloat)Iccn - Iccc,
              izb = (Tfloat)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(x,y,z,k) = -amp*cimg::sign(itt)*cimg::abs(it);
          }
        } else cimg_forV(*this,k) cimg_for3x3x3(*this,x,y,z,k,I) veloc(x,y,z,k) = -(Tfloat)Ipcc-Incc-Icpc-Icnc-Iccp-Iccn+6*Iccc; // 3D Inverse diffusion.
      } else {
        CImg_3x3(I,T);
        if (sharpen_type) { // 2D Shock filter.
          CImg<Tfloat> G = (alpha>0?get_blur(alpha).get_structure_tensor():get_structure_tensor());
          if (sigma>0) G.blur(sigma);
          cimg_forXY(G,x,y) {
            G.get_tensor_at(x,y).symmetric_eigen(val,vec);
            G(x,y,0) = vec(0,0);
            G(x,y,1) = vec(0,1);
            G(x,y,2) = 1 - (Tfloat)cimg_std::pow(1+val[0]+val[1],-(Tfloat)nedge);
          }
          cimg_forV(*this,k) cimg_for3x3(*this,x,y,0,k,I) {
            const Tfloat
              u = G(x,y,0),
              v = G(x,y,1),
              amp = G(x,y,2),
              ixx = (Tfloat)Inc + Ipc - 2*Icc,
              ixy = 0.25f*((Tfloat)Inn + Ipp - Inp - Ipn),
              iyy = (Tfloat)Icn + Icp - 2*Icc,
              ixf = (Tfloat)Inc - Icc,
              ixb = (Tfloat)Icc - Ipc,
              iyf = (Tfloat)Icn - Icc,
              iyb = (Tfloat)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(x,y,k) = -amp*cimg::sign(itt)*cimg::abs(it);
          }
        } else cimg_forV(*this,k) cimg_for3x3(*this,x,y,0,k,I) veloc(x,y,k) = -(Tfloat)Ipc-Inc-Icp-Icn+4*Icc;  // 3D Inverse diffusion.
      }
      float m, M = (float)veloc.maxmin(m);
      const float vmax = (float)cimg::max(cimg::abs(m),cimg::abs(M));
      if (vmax!=0) { veloc*=amplitude/vmax; (*this)+=veloc; }
      return cut(valm,valM);
    }

    CImg<T> get_sharpen(const float amplitude, const bool sharpen_type=false, const float edge=1, const float alpha=0, const float sigma=0) const {
      return (+*this).sharpen(amplitude,sharpen_type,edge,alpha,sigma);
    }

    //! Compute the Haar multiscale wavelet transform (monodimensional version).
    /**
       \param axis Axis considered for the transform.
       \param invert Set inverse of direct transform.
       \param nb_scales Number of scales used for the transform.
    **/
    CImg<T>& haar(const char axis, const bool invert=false, const unsigned int nb_scales=1) {
      return get_haar(axis,invert,nb_scales).transfer_to(*this);
    }

    CImg<Tfloat> get_haar(const char axis, const bool invert=false, const unsigned int nb_scales=1) const {
      if (is_empty() || !nb_scales) return *this;
      CImg<Tfloat> res;

      if (nb_scales==1) {
        switch (cimg::uncase(axis)) { // Single scale transform
        case 'x' : {
          const unsigned int w = width/2;
          if (w) {
            if (w%2)
              throw CImgInstanceException("CImg<%s>::haar() : Sub-image width = %u is not even at a particular scale (=%u).",
                                          pixel_type(),w);
            res.assign(width,height,depth,dim);
            if (invert) cimg_forYZV(*this,y,z,v) { // Inverse transform along X
              for (unsigned int x=0, xw=w, x2=0; x<w; ++x, ++xw) {
                const Tfloat val0 = (Tfloat)(*this)(x,y,z,v), val1 = (Tfloat)(*this)(xw,y,z,v);
                res(x2++,y,z,v) = val0 - val1;
                res(x2++,y,z,v) = val0 + val1;
              }
            } else cimg_forYZV(*this,y,z,v) { // Direct transform along X
              for (unsigned int x=0, xw=w, x2=0; x<w; ++x, ++xw) {
                const Tfloat val0 = (Tfloat)(*this)(x2++,y,z,v), val1 = (Tfloat)(*this)(x2++,y,z,v);
                res(x,y,z,v) = (val0 + val1)/2;
                res(xw,y,z,v) = (val1 - val0)/2;
              }
            }
          } else return *this;
        } break;
        case 'y' : {
          const unsigned int h = height/2;
          if (h) {
            if (h%2)
              throw CImgInstanceException("CImg<%s>::haar() : Sub-image height = %u is not even at a particular scale.",
                                          pixel_type(),h);
            res.assign(width,height,depth,dim);
            if (invert) cimg_forXZV(*this,x,z,v) { // Inverse transform along Y
              for (unsigned int y=0, yh=h, y2=0; y<h; ++y, ++yh) {
                const Tfloat val0 = (Tfloat)(*this)(x,y,z,v), val1 = (Tfloat)(*this)(x,yh,z,v);
                res(x,y2++,z,v) = val0 - val1;
                res(x,y2++,z,v) = val0 + val1;
              }
            } else cimg_forXZV(*this,x,z,v) {
              for (unsigned int y=0, yh=h, y2=0; y<h; ++y, ++yh) { // Direct transform along Y
                const Tfloat val0 = (Tfloat)(*this)(x,y2++,z,v), val1 = (Tfloat)(*this)(x,y2++,z,v);
                res(x,y,z,v) = (val0 + val1)/2;
                res(x,yh,z,v) = (val1 - val0)/2;
              }
            }
          } else return *this;
        } break;
        case 'z' : {
          const unsigned int d = depth/2;
          if (d) {
            if (d%2)
              throw CImgInstanceException("CImg<%s>::haar() : Sub-image depth = %u is not even at a particular scale.",
                                          pixel_type(),d);
            res.assign(width,height,depth,dim);
            if (invert) cimg_forXYV(*this,x,y,v) { // Inverse transform along Z
              for (unsigned int z=0, zd=d, z2=0; z<d; ++z, ++zd) {
                const Tfloat val0 = (Tfloat)(*this)(x,y,z,v), val1 = (Tfloat)(*this)(x,y,zd,v);
                res(x,y,z2++,v) = val0 - val1;
                res(x,y,z2++,v) = val0 + val1;
              }
            } else cimg_forXYV(*this,x,y,v) {
              for (unsigned int z=0, zd=d, z2=0; z<d; ++z, ++zd) { // Direct transform along Z
                const Tfloat val0 = (Tfloat)(*this)(x,y,z2++,v), val1 = (Tfloat)(*this)(x,y,z2++,v);
                res(x,y,z,v) = (val0 + val1)/2;
                res(x,y,zd,v) = (val1 - val0)/2;
              }
            }
          } else return *this;
        } break;
        default :
          throw CImgArgumentException("CImg<%s>::haar() : Invalid axis '%c', must be 'x','y' or 'z'.",
                                      pixel_type(),axis);
        }
      } else { // Multi-scale version
        if (invert) {
          res.assign(*this);
          switch (cimg::uncase(axis)) {
          case 'x' : {
            unsigned int w = width;
            for (unsigned int s=1; w && s<nb_scales; ++s) w/=2;
            for (w=w?w:1; w<=width; w*=2) res.draw_image(res.get_crop(0,w-1).get_haar('x',true,1));
          } break;
          case 'y' : {
            unsigned int h = width;
            for (unsigned int s=1; h && s<nb_scales; ++s) h/=2;
            for (h=h?h:1; h<=height; h*=2) res.draw_image(res.get_crop(0,0,width-1,h-1).get_haar('y',true,1));
          } break;
          case 'z' : {
            unsigned int d = depth;
            for (unsigned int s=1; d && s<nb_scales; ++s) d/=2;
            for (d=d?d:1; d<=depth; d*=2) res.draw_image(res.get_crop(0,0,0,width-1,height-1,d-1).get_haar('z',true,1));
          } break;
          default :
            throw CImgArgumentException("CImg<%s>::haar() : Invalid axis '%c', must be 'x','y' or 'z'.",
                                        pixel_type(),axis);
          }
        } else { // Direct transform
          res = get_haar(axis,false,1);
          switch (cimg::uncase(axis)) {
          case 'x' : {
            for (unsigned int s=1, w=width/2; w && s<nb_scales; ++s, w/=2) res.draw_image(res.get_crop(0,w-1).get_haar('x',false,1));
          } break;
          case 'y' : {
            for (unsigned int s=1, h=height/2; h && s<nb_scales; ++s, h/=2) res.draw_image(res.get_crop(0,0,width-1,h-1).get_haar('y',false,1));
          } break;
          case 'z' : {
            for (unsigned int s=1, d=depth/2; d && s<nb_scales; ++s, d/=2) res.draw_image(res.get_crop(0,0,0,width-1,height-1,d-1).get_haar('z',false,1));
          } break;
          default :
            throw CImgArgumentException("CImg<%s>::haar() : Invalid axis '%c', must be 'x','y' or 'z'.",
                                        pixel_type(),axis);
          }
        }
      }
      return res;
    }

    //! Compute the Haar multiscale wavelet transform.
    /**
       \param invert Set inverse of direct transform.
       \param nb_scales Number of scales used for the transform.
    **/
    CImg<T>& haar(const bool invert=false, const unsigned int nb_scales=1) {
      return get_haar(invert,nb_scales).transfer_to(*this);
    }

    CImg<Tfloat> get_haar(const bool invert=false, const unsigned int nb_scales=1) const {
      CImg<Tfloat> res;

      if (nb_scales==1) { // Single scale transform
        if (width>1) get_haar('x',invert,1).transfer_to(res);
        if (height>1) { if (res) res.get_haar('y',invert,1).transfer_to(res); else get_haar('y',invert,1).transfer_to(res); }
        if (depth>1) { if (res) res.get_haar('z',invert,1).transfer_to(res); else get_haar('z',invert,1).transfer_to(res); }
        if (res) return res;
      } else { // Multi-scale transform
        if (invert) { // Inverse transform
          res.assign(*this);
          if (width>1) {
            if (height>1) {
              if (depth>1) {
                unsigned int w = width, h = height, d = depth; for (unsigned int s=1; w && h && d && s<nb_scales; ++s) { w/=2; h/=2; d/=2; }
                for (w=w?w:1, h=h?h:1, d=d?d:1; w<=width && h<=height && d<=depth; w*=2, h*=2, d*=2)
                  res.draw_image(res.get_crop(0,0,0,w-1,h-1,d-1).get_haar(true,1));
              } else {
                unsigned int w = width, h = height; for (unsigned int s=1; w && h && s<nb_scales; ++s) { w/=2; h/=2; }
                for (w=w?w:1, h=h?h:1; w<=width && h<=height; w*=2, h*=2)
                  res.draw_image(res.get_crop(0,0,0,w-1,h-1,0).get_haar(true,1));
              }
            } else {
              if (depth>1) {
                unsigned int w = width, d = depth; for (unsigned int s=1; w && d && s<nb_scales; ++s) { w/=2; d/=2; }
                for (w=w?w:1, d=d?d:1; w<=width && d<=depth; w*=2, d*=2)
                  res.draw_image(res.get_crop(0,0,0,w-1,0,d-1).get_haar(true,1));
              } else {
                unsigned int w = width; for (unsigned int s=1; w && s<nb_scales; ++s) w/=2;
                for (w=w?w:1; w<=width; w*=2)
                  res.draw_image(res.get_crop(0,0,0,w-1,0,0).get_haar(true,1));
              }
            }
          } else {
            if (height>1) {
              if (depth>1) {
                unsigned int h = height, d = depth; for (unsigned int s=1; h && d && s<nb_scales; ++s) { h/=2; d/=2; }
                for (h=h?h:1, d=d?d:1; h<=height && d<=depth; h*=2, d*=2)
                  res.draw_image(res.get_crop(0,0,0,0,h-1,d-1).get_haar(true,1));
              } else {
                unsigned int h = height; for (unsigned int s=1; h && s<nb_scales; ++s) h/=2;
                for (h=h?h:1; h<=height; h*=2)
                  res.draw_image(res.get_crop(0,0,0,0,h-1,0).get_haar(true,1));
              }
            } else {
              if (depth>1) {
                unsigned int d = depth; for (unsigned int s=1; d && s<nb_scales; ++s) d/=2;
                for (d=d?d:1; d<=depth; d*=2)
                  res.draw_image(res.get_crop(0,0,0,0,0,d-1).get_haar(true,1));
              } else return *this;
            }
          }
        } else { // Direct transform
          res = get_haar(false,1);
          if (width>1) {
            if (height>1) {
              if (depth>1) for (unsigned int s=1, w=width/2, h=height/2, d=depth/2; w && h && d && s<nb_scales; ++s, w/=2, h/=2, d/=2)
                res.draw_image(res.get_crop(0,0,0,w-1,h-1,d-1).haar(false,1));
              else for (unsigned int s=1, w=width/2, h=height/2; w && h && s<nb_scales; ++s, w/=2, h/=2)
                res.draw_image(res.get_crop(0,0,0,w-1,h-1,0).haar(false,1));
            } else {
              if (depth>1) for (unsigned int s=1, w=width/2, d=depth/2; w && d && s<nb_scales; ++s, w/=2, d/=2)
                res.draw_image(res.get_crop(0,0,0,w-1,0,d-1).haar(false,1));
              else for (unsigned int s=1, w=width/2; w && s<nb_scales; ++s, w/=2)
                res.draw_image(res.get_crop(0,0,0,w-1,0,0).haar(false,1));
            }
          } else {
            if (height>1) {
              if (depth>1) for (unsigned int s=1, h=height/2, d=depth/2; h && d && s<nb_scales; ++s, h/=2, d/=2)
                res.draw_image(res.get_crop(0,0,0,0,h-1,d-1).haar(false,1));
              else for (unsigned int s=1, h=height/2; h && s<nb_scales; ++s, h/=2)
                res.draw_image(res.get_crop(0,0,0,0,h-1,0).haar(false,1));
            } else {
              if (depth>1) for (unsigned int s=1, d=depth/2; d && s<nb_scales; ++s, d/=2)
                res.draw_image(res.get_crop(0,0,0,0,0,d-1).haar(false,1));
              else return *this;
            }
          }
        }
        return res;
      }
      return *this;
    }

    //! Estimate a displacement field between instance image and given target image.
    CImg<T>& displacement_field(const CImg<T>& target, const float smooth=0.1f, const float precision=0.1f,
                                const unsigned int nb_scales=0, const unsigned int itermax=10000) {
      return get_displacement_field(target,smooth,precision,nb_scales,itermax).transfer_to(*this);
    }

    CImg<Tfloat> get_displacement_field(const CImg<T>& target,
                                        const float smoothness=0.1f, const float precision=0.1f,
                                        const unsigned int nb_scales=0, const unsigned int itermax=10000) const {
      if (is_empty() || !target) return *this;
      if (!is_sameXYZV(target))
        throw CImgArgumentException("CImg<%s>::displacement_field() : Instance image (%u,%u,%u,%u,%p) and target image (%u,%u,%u,%u,%p) "
                                    "have different size.",
                                    pixel_type(),width,height,depth,dim,data,
                                    target.width,target.height,target.depth,target.dim,target.data);
      if (smoothness<0)
        throw CImgArgumentException("CImg<%s>::displacement_field() : Smoothness parameter %g is negative.",
                                    pixel_type(),smoothness);
      if (precision<0)
        throw CImgArgumentException("CImg<%s>::displacement_field() : Precision parameter %g is negative.",
                                    pixel_type(),precision);

      const unsigned int nscales = nb_scales>0?nb_scales:(unsigned int)(2*cimg_std::log((double)(cimg::max(width,height,depth))));
      Tfloat m1, M1 = (Tfloat)maxmin(m1), m2, M2 = (Tfloat)target.maxmin(m2);
      const Tfloat factor = cimg::max(cimg::abs(m1),cimg::abs(M1),cimg::abs(m2),cimg::abs(M2));
      CImg<Tfloat> U0;
      const bool threed = (depth>1);

      // Begin multi-scale motion estimation
      for (int scale = (int)nscales-1; scale>=0; --scale) {
        const float sfactor = (float)cimg_std::pow(1.5f,(float)scale), sprecision = (float)(precision/cimg_std::pow(2.25,1+scale));
        const int
          sw = (int)(width/sfactor), sh = (int)(height/sfactor), sd = (int)(depth/sfactor),
          swidth = sw?sw:1, sheight = sh?sh:1, sdepth = sd?sd:1;
        CImg<Tfloat>
          I1 = get_resize(swidth,sheight,sdepth,-100,2),
          I2 = target.get_resize(swidth,sheight,sdepth,-100,2);
        I1/=factor; I2/=factor;
        CImg<Tfloat> U;
        if (U0) U = (U0*=1.5f).get_resize(I1.dimx(),I1.dimy(),I1.dimz(),-100,3);
        else U.assign(I1.dimx(),I1.dimy(),I1.dimz(),threed?3:2,0);

        // Begin single-scale motion estimation
        CImg<Tfloat> veloc(U);
        float dt = 2, Energy = cimg::type<float>::max();
        const CImgList<Tfloat> dI = I2.get_gradient();
        for (unsigned int iter=0; iter<itermax; iter++) {
          veloc.fill(0);
          float nEnergy = 0;
          if (threed) {
            cimg_for3XYZ(U,x,y,z) {
              const float X = (float)(x + U(x,y,z,0)), Y = (float)(y + U(x,y,z,1)), Z = (float)(z + U(x,y,z,2));
              cimg_forV(U,k) {
                const Tfloat
                  Ux = 0.5f*(U(_n1x,y,z,k) - U(_p1x,y,z,k)),
                  Uy = 0.5f*(U(x,_n1y,z,k) - U(x,_p1y,z,k)),
                  Uz = 0.5f*(U(x,y,_n1z,k) - U(x,y,_p1z,k)),
                  Uxx = U(_n1x,y,z,k) + U(_p1x,y,z,k) - 2*U(x,y,z,k),
                  Uyy = U(x,_n1y,z,k) + U(x,_p1y,z,k) - 2*U(x,y,z,k),
                  Uzz = U(x,y,_n1z,k) + U(x,y,_n1z,k) - 2*U(x,y,z,k);
                nEnergy += (float)(smoothness*(Ux*Ux + Uy*Uy + Uz*Uz));
                Tfloat deltaIgrad = 0;
                cimg_forV(I1,i) {
                  const Tfloat deltaIi = (float)(I2._linear_atXYZ(X,Y,Z,i) - I1(x,y,z,i));
                  nEnergy += (float)(deltaIi*deltaIi/2);
                  deltaIgrad+=-deltaIi*dI[k]._linear_atXYZ(X,Y,Z,i);
                }
                veloc(x,y,z,k) = deltaIgrad + smoothness*(Uxx + Uyy + Uzz);
              }
            }
          } else {
            cimg_for3XY(U,x,y) {
              const float X = (float)(x + U(x,y,0)), Y = (float)(y + U(x,y,1));
              cimg_forV(U,k) {
                const Tfloat
                  Ux = 0.5f*(U(_n1x,y,k) - U(_p1x,y,k)),
                  Uy = 0.5f*(U(x,_n1y,k) - U(x,_p1y,k)),
                  Uxx = U(_n1x,y,k) + U(_p1x,y,k) - 2*U(x,y,k),
                  Uyy = U(x,_n1y,k) + U(x,_p1y,k) - 2*U(x,y,k);
                nEnergy += (float)(smoothness*(Ux*Ux + Uy*Uy));
                Tfloat deltaIgrad = 0;
                cimg_forV(I1,i) {
                  const Tfloat deltaIi = (float)(I2._linear_atXY(X,Y,i) - I1(x,y,i));
                  nEnergy += (float)(deltaIi*deltaIi/2);
                  deltaIgrad+=-deltaIi*dI[k]._linear_atXY(X,Y,i);
                }
                veloc(x,y,k) = deltaIgrad + smoothness*(Uxx + Uyy);
              }
            }
          }
          const Tfloat vmax = cimg::max(cimg::abs(veloc.min()), cimg::abs(veloc.max()));
          U+=(veloc*=dt/vmax);
          if (cimg::abs(nEnergy-Energy)<sprecision) break;
          if (nEnergy<Energy) dt*=0.5f;
          Energy = nEnergy;
        }
        U.transfer_to(U0);
      }
      return U0;
    }

    //@}
    //-----------------------------
    //
    //! \name Matrix and Vectors
    //@{
    //-----------------------------

    //! Return a vector with specified coefficients.
    static CImg<T> vector(const T& a0) {
      static CImg<T> r(1,1); r[0] = a0;
      return r;
    }

    //! Return a vector with specified coefficients.
    static CImg<T> vector(const T& a0, const T& a1) {
      static CImg<T> r(1,2); T *ptr = r.data;
      *(ptr++) = a0; *(ptr++) = a1;
      return r;
    }

    //! Return a vector with specified coefficients.
    static CImg<T> vector(const T& a0, const T& a1, const T& a2) {
      static CImg<T> r(1,3); T *ptr = r.data;
      *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2;
      return r;
    }

    //! Return a vector with specified coefficients.
    static CImg<T> vector(const T& a0, const T& a1, const T& a2, const T& a3) {
      static CImg<T> r(1,4); T *ptr = r.data;
      *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3;
      return r;
    }

    //! Return a vector with specified coefficients.
    static CImg<T> vector(const T& a0, const T& a1, const T& a2, const T& a3, const T& a4) {
      static CImg<T> r(1,5); T *ptr = r.data;
      *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3; *(ptr++) = a4;
      return r;
    }

    //! Return a vector with specified coefficients.
    static CImg<T> vector(const T& a0, const T& a1, const T& a2, const T& a3, const T& a4, const T& a5) {
      static CImg<T> r(1,6); T *ptr = r.data;
      *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3; *(ptr++) = a4; *(ptr++) = a5;
      return r;
    }

    //! Return a vector with specified coefficients.
    static CImg<T> vector(const T& a0, const T& a1, const T& a2, const T& a3,
                          const T& a4, const T& a5, const T& a6) {
      static CImg<T> r(1,7); T *ptr = r.data;
      *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3;
      *(ptr++) = a4; *(ptr++) = a5; *(ptr++) = a6;
      return r;
    }

    //! Return a vector with specified coefficients.
    static CImg<T> vector(const T& a0, const T& a1, const T& a2, const T& a3,
                          const T& a4, const T& a5, const T& a6, const T& a7) {
      static CImg<T> r(1,8); T *ptr = r.data;
      *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3;
      *(ptr++) = a4; *(ptr++) = a5; *(ptr++) = a6; *(ptr++) = a7;
      return r;
    }

    //! Return a vector with specified coefficients.
    static CImg<T> vector(const T& a0, const T& a1, const T& a2, const T& a3,
                          const T& a4, const T& a5, const T& a6, const T& a7,
                          const T& a8) {
      static CImg<T> r(1,9); T *ptr = r.data;
      *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3;
      *(ptr++) = a4; *(ptr++) = a5; *(ptr++) = a6; *(ptr++) = a7;
      *(ptr++) = a8;
      return r;
    }

    //! Return a vector with specified coefficients.
    static CImg<T> vector(const T& a0, const T& a1, const T& a2, const T& a3,
                          const T& a4, const T& a5, const T& a6, const T& a7,
                          const T& a8, const T& a9) {
      static CImg<T> r(1,10); T *ptr = r.data;
      *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3;
      *(ptr++) = a4; *(ptr++) = a5; *(ptr++) = a6; *(ptr++) = a7;
      *(ptr++) = a8; *(ptr++) = a9;
      return r;
    }

    //! Return a vector with specified coefficients.
    static CImg<T> vector(const T& a0, const T& a1, const T& a2, const T& a3,
                          const T& a4, const T& a5, const T& a6, const T& a7,
                          const T& a8, const T& a9, const T& a10) {
      static CImg<T> r(1,11); T *ptr = r.data;
      *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3;
      *(ptr++) = a4; *(ptr++) = a5; *(ptr++) = a6; *(ptr++) = a7;
      *(ptr++) = a8; *(ptr++) = a9; *(ptr++) = a10;
      return r;
    }

    //! Return a vector with specified coefficients.
    static CImg<T> vector(const T& a0, const T& a1, const T& a2, const T& a3,
                          const T& a4, const T& a5, const T& a6, const T& a7,
                          const T& a8, const T& a9, const T& a10, const T& a11) {
      static CImg<T> r(1,12); T *ptr = r.data;
      *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3;
      *(ptr++) = a4; *(ptr++) = a5; *(ptr++) = a6; *(ptr++) = a7;
      *(ptr++) = a8; *(ptr++) = a9; *(ptr++) = a10; *(ptr++) = a11;
      return r;
    }

    //! Return a vector with specified coefficients.
    static CImg<T> vector(const T& a0, const T& a1, const T& a2, const T& a3,
                          const T& a4, const T& a5, const T& a6, const T& a7,
                          const T& a8, const T& a9, const T& a10, const T& a11,
                          const T& a12) {
      static CImg<T> r(1,13); T *ptr = r.data;
      *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3;
      *(ptr++) = a4; *(ptr++) = a5; *(ptr++) = a6; *(ptr++) = a7;
      *(ptr++) = a8; *(ptr++) = a9; *(ptr++) = a10; *(ptr++) = a11;
      *(ptr++) = a12;
      return r;
    }

    //! Return a vector with specified coefficients.
    static CImg<T> vector(const T& a0, const T& a1, const T& a2, const T& a3,
                          const T& a4, const T& a5, const T& a6, const T& a7,
                          const T& a8, const T& a9, const T& a10, const T& a11,
                          const T& a12, const T& a13) {
      static CImg<T> r(1,14); T *ptr = r.data;
      *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3;
      *(ptr++) = a4; *(ptr++) = a5; *(ptr++) = a6; *(ptr++) = a7;
      *(ptr++) = a8; *(ptr++) = a9; *(ptr++) = a10; *(ptr++) = a11;
      *(ptr++) = a12; *(ptr++) = a13;
      return r;
    }

    //! Return a vector with specified coefficients.
    static CImg<T> vector(const T& a0, const T& a1, const T& a2, const T& a3,
                          const T& a4, const T& a5, const T& a6, const T& a7,
                          const T& a8, const T& a9, const T& a10, const T& a11,
                          const T& a12, const T& a13, const T& a14) {
      static CImg<T> r(1,15); T *ptr = r.data;
      *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3;
      *(ptr++) = a4; *(ptr++) = a5; *(ptr++) = a6; *(ptr++) = a7;
      *(ptr++) = a8; *(ptr++) = a9; *(ptr++) = a10; *(ptr++) = a11;
      *(ptr++) = a12; *(ptr++) = a13; *(ptr++) = a14;
      return r;
    }

    //! Return a vector with specified coefficients.
    static CImg<T> vector(const T& a0, const T& a1, const T& a2, const T& a3,
                          const T& a4, const T& a5, const T& a6, const T& a7,
                          const T& a8, const T& a9, const T& a10, const T& a11,
                          const T& a12, const T& a13, const T& a14, const T& a15) {
      static CImg<T> r(1,16); T *ptr = r.data;
      *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3;
      *(ptr++) = a4; *(ptr++) = a5; *(ptr++) = a6; *(ptr++) = a7;
      *(ptr++) = a8; *(ptr++) = a9; *(ptr++) = a10; *(ptr++) = a11;
      *(ptr++) = a12; *(ptr++) = a13; *(ptr++) = a14; *(ptr++) = a15;
      return r;
    }

    //! Return a 1x1 square matrix with specified coefficients.
    static CImg<T> matrix(const T& a0) {
      return vector(a0);
    }

    //! Return a 2x2 square matrix with specified coefficients.
    static CImg<T> matrix(const T& a0, const T& a1,
                          const T& a2, const T& a3) {
      static CImg<T> r(2,2); T *ptr = r.data;
      *(ptr++) = a0; *(ptr++) = a1;
      *(ptr++) = a2; *(ptr++) = a3;
      return r;
    }

    //! Return a 3x3 square matrix with specified coefficients.
    static CImg<T> matrix(const T& a0, const T& a1, const T& a2,
                          const T& a3, const T& a4, const T& a5,
                          const T& a6, const T& a7, const T& a8) {
      static CImg<T> r(3,3); T *ptr = r.data;
      *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2;
      *(ptr++) = a3; *(ptr++) = a4; *(ptr++) = a5;
      *(ptr++) = a6; *(ptr++) = a7; *(ptr++) = a8;
      return r;
    }

    //! Return a 4x4 square matrix with specified coefficients.
    static CImg<T> matrix(const T& a0, const T& a1, const T& a2, const T& a3,
                          const T& a4, const T& a5, const T& a6, const T& a7,
                          const T& a8, const T& a9, const T& a10, const T& a11,
                          const T& a12, const T& a13, const T& a14, const T& a15) {
      static CImg<T> r(4,4); T *ptr = r.data;
      *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3;
      *(ptr++) = a4; *(ptr++) = a5; *(ptr++) = a6; *(ptr++) = a7;
      *(ptr++) = a8; *(ptr++) = a9; *(ptr++) = a10; *(ptr++) = a11;
      *(ptr++) = a12; *(ptr++) = a13; *(ptr++) = a14; *(ptr++) = a15;
      return r;
    }

    //! Return a 5x5 square matrix with specified coefficients.
    static CImg<T> matrix(const T& a0, const T& a1, const T& a2, const T& a3, const T& a4,
                          const T& a5, const T& a6, const T& a7, const T& a8, const T& a9,
                          const T& a10, const T& a11, const T& a12, const T& a13, const T& a14,
                          const T& a15, const T& a16, const T& a17, const T& a18, const T& a19,
                          const T& a20, const T& a21, const T& a22, const T& a23, const T& a24) {
      static CImg<T> r(5,5); T *ptr = r.data;
      *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3; *(ptr++) = a4;
      *(ptr++) = a5; *(ptr++) = a6; *(ptr++) = a7; *(ptr++) = a8; *(ptr++) = a9;
      *(ptr++) = a10; *(ptr++) = a11; *(ptr++) = a12; *(ptr++) = a13; *(ptr++) = a14;
      *(ptr++) = a15; *(ptr++) = a16; *(ptr++) = a17; *(ptr++) = a18; *(ptr++) = a19;
      *(ptr++) = a20; *(ptr++) = a21; *(ptr++) = a22; *(ptr++) = a23; *(ptr++) = a24;
      return r;
    }

    //! Return a 1x1 symmetric matrix with specified coefficients.
    static CImg<T> tensor(const T& a1) {
      return matrix(a1);
    }

    //! Return a 2x2 symmetric matrix tensor with specified coefficients.
    static CImg<T> tensor(const T& a1, const T& a2, const T& a3) {
      return matrix(a1,a2,a2,a3);
    }

    //! Return a 3x3 symmetric matrix with specified coefficients.
    static CImg<T> tensor(const T& a1, const T& a2, const T& a3, const T& a4, const T& a5, const T& a6) {
      return matrix(a1,a2,a3,a2,a4,a5,a3,a5,a6);
    }

    //! Return a 1x1 diagonal matrix with specified coefficients.
    static CImg<T> diagonal(const T& a0) {
      return matrix(a0);
    }

    //! Return a 2x2 diagonal matrix with specified coefficients.
    static CImg<T> diagonal(const T& a0, const T& a1) {
      return matrix(a0,0,0,a1);
    }

    //! Return a 3x3 diagonal matrix with specified coefficients.
    static CImg<T> diagonal(const T& a0, const T& a1, const T& a2) {
      return matrix(a0,0,0,0,a1,0,0,0,a2);
    }

    //! Return a 4x4 diagonal matrix with specified coefficients.
    static CImg<T> diagonal(const T& a0, const T& a1, const T& a2, const T& a3) {
      return matrix(a0,0,0,0,0,a1,0,0,0,0,a2,0,0,0,0,a3);
    }

    //! Return a 5x5 diagonal matrix with specified coefficients.
    static CImg<T> diagonal(const T& a0, const T& a1, const T& a2, const T& a3, const T& a4) {
      return matrix(a0,0,0,0,0,0,a1,0,0,0,0,0,a2,0,0,0,0,0,a3,0,0,0,0,0,a4);
    }

    //! Return a NxN identity matrix.
    static CImg<T> identity_matrix(const unsigned int N) {
      CImg<T> res(N,N,1,1,0);
      cimg_forX(res,x) res(x,x) = 1;
      return res;
    }

    //! Return a N-numbered sequence vector from \p a0 to \p a1.
    static CImg<T> sequence(const unsigned int N, const T a0, const T a1) {
      if (N) return CImg<T>(1,N).sequence(a0,a1);
      return CImg<T>();
    }

    //! Return a 3x3 rotation matrix along the (x,y,z)-axis with an angle w.
    static CImg<T> rotation_matrix(const float x, const float y, const float z, const float w, const bool quaternion_data=false) {
      float X,Y,Z,W;
      if (!quaternion_data) {
        const float norm = (float)cimg_std::sqrt(x*x + y*y + z*z),
          nx = norm>0?x/norm:0,
          ny = norm>0?y/norm:0,
          nz = norm>0?z/norm:1,
          nw = norm>0?w:0,
          sina = (float)cimg_std::sin(nw/2),
          cosa = (float)cimg_std::cos(nw/2);
        X = nx*sina;
        Y = ny*sina;
        Z = nz*sina;
        W = cosa;
      } else {
        const float norm = (float)cimg_std::sqrt(x*x + y*y + z*z + w*w);
        if (norm>0) { X = x/norm; Y = y/norm; Z = z/norm; W = w/norm; }
        else { X = Y = Z = 0; W = 1; }
      }
      const float xx = X*X, xy = X*Y, xz = X*Z, xw = X*W, yy = Y*Y, yz = Y*Z, yw = Y*W, zz = Z*Z, zw = Z*W;
      return CImg<T>::matrix((T)(1-2*(yy+zz)), (T)(2*(xy+zw)),   (T)(2*(xz-yw)),
                             (T)(2*(xy-zw)),   (T)(1-2*(xx+zz)), (T)(2*(yz+xw)),
                             (T)(2*(xz+yw)),   (T)(2*(yz-xw)),   (T)(1-2*(xx+yy)));
    }

    //! Return a new image corresponding to the vector located at (\p x,\p y,\p z) of the current vector-valued image.
    CImg<T> get_vector_at(const unsigned int x, const unsigned int y=0, const unsigned int z=0) const {
      static CImg<T> dest;
      if (dest.height!=dim) dest.assign(1,dim);
      const unsigned int whz = width*height*depth;
      const T *ptrs = ptr(x,y,z);
      T *ptrd = dest.data;
      cimg_forV(*this,k) { *(ptrd++) = *ptrs; ptrs+=whz; }
      return dest;
    }

    //! Set the image \p vec as the \a vector \a valued pixel located at (\p x,\p y,\p z) of the current vector-valued image.
    template<typename t>
    CImg<T>& set_vector_at(const CImg<t>& vec, const unsigned int x, const unsigned int y=0, const unsigned int z=0) {
      if (x<width && y<height && z<depth) {
        const unsigned int whz = width*height*depth;
        const t *ptrs = vec.data;
        T *ptrd = ptr(x,y,z);
        for (unsigned int k=cimg::min((unsigned int)vec.size(),dim); k; --k) { *ptrd = (T)*(ptrs++); ptrd+=whz; }
      }
      return *this;
    }

    //! Return a new image corresponding to the \a square \a matrix located at (\p x,\p y,\p z) of the current vector-valued image.
    CImg<T> get_matrix_at(const unsigned int x=0, const unsigned int y=0, const unsigned int z=0) const {
      const int n = (int)cimg_std::sqrt((double)dim);
      CImg<T> dest(n,n);
      cimg_forV(*this,k) dest[k]=(*this)(x,y,z,k);
      return dest;
    }

    //! Set the image \p vec as the \a square \a matrix-valued pixel located at (\p x,\p y,\p z) of the current vector-valued image.
    template<typename t>
    CImg<T>& set_matrix_at(const CImg<t>& mat, const unsigned int x=0, const unsigned int y=0, const unsigned int z=0) {
      return set_vector_at(mat,x,y,z);
    }

    //! Return a new image corresponding to the \a diffusion \a tensor located at (\p x,\p y,\p z) of the current vector-valued image.
    CImg<T> get_tensor_at(const unsigned int x, const unsigned int y=0, const unsigned int z=0) const {
      if (dim==6) return tensor((*this)(x,y,z,0),(*this)(x,y,z,1),(*this)(x,y,z,2),
                                (*this)(x,y,z,3),(*this)(x,y,z,4),(*this)(x,y,z,5));
      if (dim==3) return tensor((*this)(x,y,z,0),(*this)(x,y,z,1),(*this)(x,y,z,2));
      return tensor((*this)(x,y,z,0));
    }

    //! Set the image \p vec as the \a tensor \a valued pixel located at (\p x,\p y,\p z) of the current vector-valued image.
    template<typename t>
    CImg<T>& set_tensor_at(const CImg<t>& ten, const unsigned int x=0, const unsigned int y=0, const unsigned int z=0) {
      if (ten.height==2) {
        (*this)(x,y,z,0) = (T)ten[0];
        (*this)(x,y,z,1) = (T)ten[1];
        (*this)(x,y,z,2) = (T)ten[3];
      }
      else {
        (*this)(x,y,z,0) = (T)ten[0];
        (*this)(x,y,z,1) = (T)ten[1];
        (*this)(x,y,z,2) = (T)ten[2];
        (*this)(x,y,z,3) = (T)ten[4];
        (*this)(x,y,z,4) = (T)ten[5];
        (*this)(x,y,z,5) = (T)ten[8];
      }
      return *this;
    }

    //! Unroll all images values into a one-column vector.
    CImg<T>& vector() {
      return unroll('y');
    }

    CImg<T> get_vector() const {
      return get_unroll('y');
    }

    //! Realign pixel values of the instance image as a square matrix
    CImg<T>& matrix() {
      const unsigned int siz = size();
      switch (siz) {
      case 1 : break;
      case 4 : width = height = 2; break;
      case 9 : width = height = 3; break;
      case 16 : width = height = 4; break;
      case 25 : width = height = 5; break;
      case 36 : width = height = 6; break;
      case 49 : width = height = 7; break;
      case 64 : width = height = 8; break;
      case 81 : width = height = 9; break;
      case 100 : width = height = 10; break;
      default : {
        unsigned int i = 11, i2 = i*i;
        while (i2<siz) { i2+=2*i+1; ++i; }
        if (i2==siz) width = height = i;
        else throw CImgInstanceException("CImg<%s>::matrix() : Image size = %u is not a square number",
                                         pixel_type(),siz);
      }
      }
      return *this;
    }

    CImg<T> get_matrix() const {
      return (+*this).matrix();
    }

    //! Realign pixel values of the instance image as a symmetric tensor.
    CImg<T>& tensor() {
      return get_tensor().transfer_to(*this);
    }

    CImg<T> get_tensor() const {
      CImg<T> res;
      const unsigned int siz = size();
      switch (siz) {
      case 1 : break;
      case 3 :
        res.assign(2,2);
        res(0,0) = (*this)(0);
        res(1,0) = res(0,1) = (*this)(1);
        res(1,1) = (*this)(2);
        break;
      case 6 :
        res.assign(3,3);
        res(0,0) = (*this)(0);
        res(1,0) = res(0,1) = (*this)(1);
        res(2,0) = res(0,2) = (*this)(2);
        res(1,1) = (*this)(3);
        res(2,1) = res(1,2) = (*this)(4);
        res(2,2) = (*this)(5);
        break;
      default :
        throw CImgInstanceException("CImg<%s>::tensor() : Wrong vector dimension = %u in instance image.",
                                    pixel_type(), dim);
      }
      return res;
    }

    //! Unroll all images values into specified axis.
    CImg<T>& unroll(const char axis) {
      const unsigned int siz = size();
      if (siz) switch (axis) {
      case 'x' : width = siz; height=depth=dim=1; break;
      case 'y' : height = siz; width=depth=dim=1; break;
      case 'z' : depth = siz; width=height=dim=1; break;
      case 'v' : dim = siz; width=height=depth=1; break;
      default :
        throw CImgArgumentException("CImg<%s>::unroll() : Given axis is '%c' which is not 'x','y','z' or 'v'",
                                    pixel_type(),axis);
      }
      return *this;
    }

    CImg<T> get_unroll(const char axis) const {
      return (+*this).unroll(axis);
    }

    //! Get a diagonal matrix, whose diagonal coefficients are the coefficients of the input image.
    CImg<T>& diagonal() {
      return get_diagonal().transfer_to(*this);
    }

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

    //! Get an identity matrix having same dimension than instance image.
    CImg<T>& identity_matrix() {
      return identity_matrix(cimg::max(width,height)).transfer_to(*this);
    }

    CImg<T> get_identity_matrix() const {
      return identity_matrix(cimg::max(width,height));
    }

    //! Return a N-numbered sequence vector from \p a0 to \p a1.
    CImg<T>& sequence(const T a0, const T a1) {
      if (is_empty()) return *this;
      const unsigned int siz = size() - 1;
      T* ptr = data;
      if (siz) {
        const Tfloat delta = (Tfloat)a1 - a0;
        cimg_foroff(*this,l) *(ptr++) = (T)(a0 + delta*l/siz);
      } else *ptr = a0;
      return *this;
    }

    CImg<T> get_sequence(const T a0, const T a1) const {
      return (+*this).sequence(a0,a1);
    }

    //! Transpose the current matrix.
    CImg<T>& transpose() {
      if (width==1) { width=height; height=1; return *this; }
      if (height==1) { height=width; width=1; return *this; }
      if (width==height) {
        cimg_forYZV(*this,y,z,v) for (int x=y; x<dimx(); ++x) cimg::swap((*this)(x,y,z,v),(*this)(y,x,z,v));
        return *this;
      }
      return get_transpose().transfer_to(*this);
    }

    CImg<T> get_transpose() const {
      return get_permute_axes("yxzv");
    }

    //! Invert the current matrix.
    CImg<T>& invert(const bool use_LU=true) {
      if (!is_empty()) {
        if (width!=height || depth!=1 || dim!=1)
          throw CImgInstanceException("CImg<%s>::invert() : Instance matrix (%u,%u,%u,%u,%p) is not square.",
                                      pixel_type(),width,height,depth,dim,data);
#ifdef cimg_use_lapack
        int INFO = (int)use_LU, N = width, LWORK = 4*N, *IPIV = new int[N];
        Tfloat
          *lapA = new Tfloat[N*N],
          *WORK = new Tfloat[LWORK];
        cimg_forXY(*this,k,l) lapA[k*N+l] = (Tfloat)((*this)(k,l));
        cimg::getrf(N,lapA,IPIV,INFO);
        if (INFO)
          cimg::warn("CImg<%s>::invert() : LAPACK library function dgetrf_() returned error code %d.",
                     pixel_type(),INFO);
        else {
          cimg::getri(N,lapA,IPIV,WORK,LWORK,INFO);
          if (INFO)
            cimg::warn("CImg<%s>::invert() : LAPACK library function dgetri_() returned Error code %d",
                       pixel_type(),INFO);
        }
        if (!INFO) cimg_forXY(*this,k,l) (*this)(k,l) = (T)(lapA[k*N+l]); else fill(0);
        delete[] IPIV; delete[] lapA; delete[] WORK;
#else
        const double dete = width>3?-1.0:det();
        if (dete!=0.0 && width==2) {
          const double
            a = data[0], c = data[1],
            b = data[2], d = data[3];
          data[0] = (T)(d/dete); data[1] = (T)(-c/dete);
          data[2] = (T)(-b/dete); data[3] = (T)(a/dete);
        } else if (dete!=0.0 && width==3) {
          const double
            a = data[0], d = data[1], g = data[2],
            b = data[3], e = data[4], h = data[5],
            c = data[6], f = data[7], i = data[8];
          data[0] = (T)((i*e-f*h)/dete), data[1] = (T)((g*f-i*d)/dete), data[2] = (T)((d*h-g*e)/dete);
          data[3] = (T)((h*c-i*b)/dete), data[4] = (T)((i*a-c*g)/dete), data[5] = (T)((g*b-a*h)/dete);
          data[6] = (T)((b*f-e*c)/dete), data[7] = (T)((d*c-a*f)/dete), data[8] = (T)((a*e-d*b)/dete);
        } else {
          if (use_LU) { // LU-based inverse computation
            CImg<Tfloat> A(*this), indx, col(1,width);
            bool d;
            A._LU(indx,d);
            cimg_forX(*this,j) {
              col.fill(0);
              col(j) = 1;
              col._solve(A,indx);
              cimg_forX(*this,i) (*this)(j,i) = (T)col(i);
            }
          } else { // SVD-based inverse computation
            CImg<Tfloat> U(width,width), S(1,width), V(width,width);
            SVD(U,S,V,false);
            U.transpose();
            cimg_forY(S,k) if (S[k]!=0) S[k]=1/S[k];
            S.diagonal();
            *this = V*S*U;
          }
        }
#endif
      }
      return *this;
    }

    CImg<Tfloat> get_invert(const bool use_LU=true) const {
      return CImg<Tfloat>(*this,false).invert(use_LU);
    }

    //! Compute the pseudo-inverse (Moore-Penrose) of the matrix.
    CImg<T>& pseudoinvert() {
      return get_pseudoinvert().transfer_to(*this);
    }

    CImg<Tfloat> get_pseudoinvert() const {
      CImg<Tfloat> U, S, V;
      SVD(U,S,V);
      cimg_forX(V,x) {
        const Tfloat s = S(x), invs = s!=0?1/s:(Tfloat)0;
        cimg_forY(V,y) V(x,y)*=invs;
      }
      return V*U.transpose();
    }

    //! Compute the cross product between two 3d vectors.
    template<typename t>
    CImg<T>& cross(const CImg<t>& img) {
      if (width!=1 || height<3 || img.width!=1 || img.height<3)
        throw CImgInstanceException("CImg<%s>::cross() : Arguments (%u,%u,%u,%u,%p) and (%u,%u,%u,%u,%p) must be both 3d vectors.",
                                    pixel_type(),width,height,depth,dim,data,img.width,img.height,img.depth,img.dim,img.data);
      const T x = (*this)[0], y = (*this)[1], z = (*this)[2];
      (*this)[0] = (T)(y*img[2]-z*img[1]);
      (*this)[1] = (T)(z*img[0]-x*img[2]);
      (*this)[2] = (T)(x*img[1]-y*img[0]);
      return *this;
    }

    template<typename t>
    CImg<typename cimg::superset<T,t>::type> get_cross(const CImg<t>& img) const {
      typedef typename cimg::superset<T,t>::type Tt;
      return CImg<Tt>(*this).cross(img);
    }

    //! Solve a linear system AX=B where B=*this.
    template<typename t>
    CImg<T>& solve(const CImg<t>& A) {
      if (width!=1 || depth!=1 || dim!=1 || height!=A.height || A.depth!=1 || A.dim!=1)
        throw CImgArgumentException("CImg<%s>::solve() : Instance matrix size is (%u,%u,%u,%u) while "
                                    "size of given matrix A is (%u,%u,%u,%u).",
                                    pixel_type(),width,height,depth,dim,A.width,A.height,A.depth,A.dim);

      typedef typename cimg::superset2<T,t,float>::type Ttfloat;
      if (A.width==A.height) {
#ifdef cimg_use_lapack
        char TRANS='N';
        int INFO, N = height, LWORK = 4*N, one = 1, *IPIV = new int[N];
        Ttfloat
          *lapA = new Ttfloat[N*N],
          *lapB = new Ttfloat[N],
          *WORK = new Ttfloat[LWORK];
        cimg_forXY(A,k,l) lapA[k*N+l] = (Ttfloat)(A(k,l));
        cimg_forY(*this,i) lapB[i] = (Ttfloat)((*this)(i));
        cimg::getrf(N,lapA,IPIV,INFO);
        if (INFO)
          cimg::warn("CImg<%s>::solve() : LAPACK library function dgetrf_() returned error code %d.",
                     pixel_type(),INFO);
        if (!INFO) {
          cimg::getrs(TRANS,N,lapA,IPIV,lapB,INFO);
          if (INFO)
            cimg::warn("CImg<%s>::solve() : LAPACK library function dgetrs_() returned Error code %d",
                       pixel_type(),INFO);
        }
        if (!INFO) cimg_forY(*this,i) (*this)(i) = (T)(lapB[i]); else fill(0);
        delete[] IPIV; delete[] lapA; delete[] lapB; delete[] WORK;
#else
        CImg<Ttfloat> lu(A);
        CImg<Ttfloat> indx;
        bool d;
        lu._LU(indx,d);
        _solve(lu,indx);
#endif
      } else assign(A.get_pseudoinvert()*(*this));
      return *this;
    }

    template<typename t>
    CImg<typename cimg::superset2<T,t,float>::type> get_solve(const CImg<t>& A) const {
      typedef typename cimg::superset2<T,t,float>::type Ttfloat;
      return CImg<Ttfloat>(*this,false).solve(A);
    }

    template<typename t, typename ti>
    CImg<T>& _solve(const CImg<t>& A, const CImg<ti>& indx) {
      typedef typename cimg::superset2<T,t,float>::type Ttfloat;
      const int N = size();
      int ii = -1;
      Ttfloat sum;
      for (int i=0; i<N; ++i) {
        const int ip = (int)indx[i];
        Ttfloat sum = (*this)(ip);
        (*this)(ip) = (*this)(i);
        if (ii>=0) for (int j=ii; j<=i-1; ++j) sum-=A(j,i)*(*this)(j);
        else if (sum!=0) ii=i;
        (*this)(i) = (T)sum;
      }
      { for (int i=N-1; i>=0; --i) {
        sum = (*this)(i);
        for (int j=i+1; j<N; ++j) sum-=A(j,i)*(*this)(j);
        (*this)(i) = (T)(sum/A(i,i));
      }}
      return *this;
    }

    //! Solve a linear system AX=B where B=*this and A is a tridiagonal matrix A = [ b0,c0,0,...; a1,b1,c1,0,... ; ... ; ...,0,aN,bN ].
    // (Use the Thomas Algorithm).
    template<typename t>
    CImg<T>& solve_tridiagonal(const CImg<t>& a, const CImg<t>& b, const CImg<t>& c) {
      const int siz = (int)size();
      if ((int)a.size()!=siz || (int)b.size()!=siz || (int)c.size()!=siz)
        throw CImgArgumentException("CImg<%s>::solve_tridiagonal() : arrays of triagonal coefficients have different size.",pixel_type);
      typedef typename cimg::superset2<T,t,float>::type Ttfloat;
      CImg<Ttfloat> nc(siz);
      const T *ptra = a.data, *ptrb = b.data, *ptrc = c.data;
      T *ptrnc = nc.data, *ptrd = data;
      const Ttfloat valb0 = (Ttfloat)*(ptrb++);
      *ptrnc = *(ptrc++)/valb0;
      Ttfloat vald = (Ttfloat)(*(ptrd++)/=valb0);
      for (int i = 1; i<siz; ++i) {
        const Ttfloat
          vala = (Tfloat)*(ptra++),
          id = 1/(*(ptrb++) - *(ptrnc++)*vala);
        *ptrnc = *(ptrc++)*id;
        vald = ((*ptrd-=vala*vald)*=id);
        ++ptrd;
      }
      vald = *(--ptrd);
      for (int i = siz-2; i>=0; --i) vald = (*(--ptrd)-=*(--ptrnc)*vald);
      return *this;
    }

    template<typename t>
    CImg<typename cimg::superset2<T,t,float>::type> get_solve_tridiagonal(const CImg<t>& a, const CImg<t>& b, const CImg<t>& c) const {
      typedef typename cimg::superset2<T,t,float>::type Ttfloat;
      return CImg<Ttfloat>(*this,false).solve_tridiagonal(a,b,c);
    }

    //! Sort values of a vector and get permutations.
    template<typename t>
    CImg<T>& sort(CImg<t>& permutations, const bool increasing=true) {
      if (is_empty()) permutations.assign();
      else {
        if (permutations.size()!=size()) permutations.assign(size());
        cimg_foroff(permutations,off) permutations[off] = (t)off;
        _quicksort(0,size()-1,permutations,increasing);
      }
      return *this;
    }

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

    // Sort image values.
    CImg<T>& sort(const bool increasing=true) {
      CImg<T> foo;
      return sort(foo,increasing);
    }

    CImg<T> get_sort(const bool increasing=true) const {
      return (+*this).sort(increasing);
    }

    template<typename t>
    CImg<T>& _quicksort(const int min, const int max, CImg<t>& permutations, const bool increasing) {
      if (min<max) {
        const int mid = (min+max)/2;
        if (increasing) {
          if ((*this)[min]>(*this)[mid]) {
            cimg::swap((*this)[min],(*this)[mid]); cimg::swap(permutations[min],permutations[mid]); }
          if ((*this)[mid]>(*this)[max]) {
            cimg::swap((*this)[max],(*this)[mid]); cimg::swap(permutations[max],permutations[mid]); }
          if ((*this)[min]>(*this)[mid]) {
            cimg::swap((*this)[min],(*this)[mid]); cimg::swap(permutations[min],permutations[mid]); }
        } else {
          if ((*this)[min]<(*this)[mid]) {
            cimg::swap((*this)[min],(*this)[mid]); cimg::swap(permutations[min],permutations[mid]); }
          if ((*this)[mid]<(*this)[max]) {
            cimg::swap((*this)[max],(*this)[mid]); cimg::swap(permutations[max],permutations[mid]); }
          if ((*this)[min]<(*this)[mid]) {
            cimg::swap((*this)[min],(*this)[mid]); cimg::swap(permutations[min],permutations[mid]); }
        }
        if (max-min>=3) {
          const T pivot = (*this)[mid];
          int i = min, j = max;
          if (increasing) {
            do {
              while ((*this)[i]<pivot) ++i;
              while ((*this)[j]>pivot) --j;
              if (i<=j) {
                cimg::swap((*this)[i],(*this)[j]);
                cimg::swap(permutations[i++],permutations[j--]);
              }
            } while (i<=j);
          } else {
            do {
              while ((*this)[i]>pivot) ++i;
              while ((*this)[j]<pivot) --j;
              if (i<=j) {
                cimg::swap((*this)[i],(*this)[j]);
                cimg::swap(permutations[i++],permutations[j--]);
              }
            } while (i<=j);
          }
          if (min<j) _quicksort(min,j,permutations,increasing);
          if (i<max) _quicksort(i,max,permutations,increasing);
        }
      }
      return *this;
    }

    //! Get a permutation of the pixels.
    template<typename t>
    CImg<T>& permute(const CImg<t>& permutation) {
      return get_permute(permutation).transfer_to(*this);
    }

    template<typename t>
    CImg<T> get_permute(const CImg<t>& permutation) const {
      if (permutation.size()!=size())
        throw CImgArgumentException("CImg<%s>::permute() : Instance image (%u,%u,%u,%u,%p) and permutation (%u,%u,%u,%u,%p)"
                                    "have different sizes.",
                                    pixel_type(),width,height,depth,dim,data,
                                    permutation.width,permutation.height,permutation.depth,permutation.dim,permutation.data);
      CImg<T> res(width,height,depth,dim);
      const t *p = permutation.ptr(permutation.size());
      cimg_for(res,ptr,T) *ptr = (*this)[*(--p)];
      return res;
    }

    //! Compute the SVD of a general matrix.
    template<typename t>
    const CImg<T>& SVD(CImg<t>& U, CImg<t>& S, CImg<t>& V,
                       const bool sorting=true, const unsigned int max_iter=40, const float lambda=0) const {
      if (is_empty()) { U.assign(); S.assign(); V.assign(); }
      else {
        U = *this;
        if (lambda!=0) {
          const unsigned int delta = cimg::min(U.width,U.height);
          for (unsigned int i=0; i<delta; ++i) U(i,i) = (t)(U(i,i) + lambda);
        }
        if (S.size()<width) S.assign(1,width);
        if (V.width<width || V.height<height) V.assign(width,width);
        CImg<t> rv1(width);
        t anorm = 0, c, f, g = 0, h, s, scale = 0;
        int l = 0, nm = 0;

        cimg_forX(U,i) {
          l = i+1; rv1[i] = scale*g; g = s = scale = 0;
          if (i<dimy()) {
            for (int k=i; k<dimy(); ++k) scale+= cimg::abs(U(i,k));
            if (scale) {
              for (int k=i; k<dimy(); ++k) { U(i,k)/=scale; s+= U(i,k)*U(i,k); }
              f = U(i,i); g = (t)((f>=0?-1:1)*cimg_std::sqrt(s)); h=f*g-s; U(i,i) = f-g;
              for (int j=l; j<dimx(); ++j) {
                s = 0; for (int k=i; k<dimy(); ++k) s+= U(i,k)*U(j,k);
                f = s/h;
                { for (int k=i; k<dimy(); ++k) U(j,k)+= f*U(i,k); }
              }
              { for (int k=i; k<dimy(); ++k) U(i,k)*= scale; }
            }
          }
          S[i]=scale*g;

          g = s = scale = 0;
          if (i<dimy() && i!=dimx()-1) {
            for (int k=l; k<dimx(); ++k) scale += cimg::abs(U(k,i));
            if (scale) {
              for (int k=l; k<dimx(); ++k) { U(k,i)/= scale; s+= U(k,i)*U(k,i); }
              f = U(l,i); g = (t)((f>=0?-1:1)*cimg_std::sqrt(s)); h = f*g-s; U(l,i) = f-g;
              { for (int k=l; k<dimx(); ++k) rv1[k]=U(k,i)/h; }
              for (int j=l; j<dimy(); ++j) {
                s = 0; for (int k=l; k<dimx(); ++k) s+= U(k,j)*U(k,i);
                { for (int k=l; k<dimx(); ++k) U(k,j)+= s*rv1[k]; }
              }
              { for (int k=l; k<dimx(); ++k) U(k,i)*= scale; }
            }
          }
          anorm = (t)cimg::max((float)anorm,(float)(cimg::abs(S[i])+cimg::abs(rv1[i])));
        }

        { for (int i=dimx()-1; i>=0; --i) {
          if (i<dimx()-1) {
            if (g) {
              { for (int j=l; j<dimx(); ++j) V(i,j) =(U(j,i)/U(l,i))/g; }
              for (int j=l; j<dimx(); ++j) {
                s = 0; for (int k=l; k<dimx(); ++k) s+= U(k,i)*V(j,k);
                { for (int k=l; k<dimx(); ++k) V(j,k)+= s*V(i,k); }
              }
            }
            for (int j=l; j<dimx(); ++j) V(j,i) = V(i,j) = (t)0.0;
          }
          V(i,i) = (t)1.0; g = rv1[i]; l = i;
        }
        }

        { for (int i=cimg::min(dimx(),dimy())-1; i>=0; --i) {
          l = i+1; g = S[i];
          for (int j=l; j<dimx(); ++j) U(j,i) = 0;
          if (g) {
            g = 1/g;
            for (int j=l; j<dimx(); ++j) {
              s = 0; for (int k=l; k<dimy(); ++k) s+= U(i,k)*U(j,k);
              f = (s/U(i,i))*g;
              { for (int k=i; k<dimy(); ++k) U(j,k)+= f*U(i,k); }
            }
            { for (int j=i; j<dimy(); ++j) U(i,j)*= g; }
          } else for (int j=i; j<dimy(); ++j) U(i,j) = 0;
          ++U(i,i);
        }
        }

        for (int k=dimx()-1; k>=0; --k) {
          for (unsigned int its=0; its<max_iter; ++its) {
            bool flag = true;
            for (l=k; l>=1; --l) {
              nm = l-1;
              if ((cimg::abs(rv1[l])+anorm)==anorm) { flag = false; break; }
              if ((cimg::abs(S[nm])+anorm)==anorm) break;
            }
            if (flag) {
              c = 0; s = 1;
              for (int i=l; i<=k; ++i) {
                f = s*rv1[i]; rv1[i] = c*rv1[i];
                if ((cimg::abs(f)+anorm)==anorm) break;
                g = S[i]; h = (t)cimg::_pythagore(f,g); S[i] = h; h = 1/h; c = g*h; s = -f*h;
                cimg_forY(U,j) { const t y = U(nm,j), z = U(i,j); U(nm,j) = y*c+z*s; U(i,j) = z*c-y*s; }
              }
            }
            const t z = S[k];
            if (l==k) { if (z<0) { S[k] = -z; cimg_forX(U,j) V(k,j) = -V(k,j); } break; }
            nm = k-1;
            t x = S[l], y = S[nm];
            g = rv1[nm]; h = rv1[k];
            f = ((y-z)*(y+z)+(g-h)*(g+h))/(2*h*y);
            g = (t)cimg::_pythagore(f,1.0);
            f = ((x-z)*(x+z)+h*((y/(f+ (f>=0?g:-g)))-h))/x;
            c = s = 1;
            for (int j=l; j<=nm; ++j) {
              const int i = j+1;
              g = rv1[i]; h = s*g; g = c*g;
              t y = S[i];
              t z = (t)cimg::_pythagore(f,h);
              rv1[j] = z; c = f/z; s = h/z;
              f = x*c+g*s; g = g*c-x*s; h = y*s; y*=c;
              cimg_forX(U,jj) { const t x = V(j,jj), z = V(i,jj); V(j,jj) = x*c+z*s; V(i,jj) = z*c-x*s; }
              z = (t)cimg::_pythagore(f,h); S[j] = z;
              if (z) { z = 1/z; c = f*z; s = h*z; }
              f = c*g+s*y; x = c*y-s*g;
              { cimg_forY(U,jj) { const t y = U(j,jj); z = U(i,jj); U(j,jj) = y*c+z*s; U(i,jj) = z*c-y*s; }}
            }
            rv1[l] = 0; rv1[k]=f; S[k]=x;
          }
        }

        if (sorting) {
          CImg<intT> permutations(width);
          CImg<t> tmp(width);
          S.sort(permutations,false);
          cimg_forY(U,k) {
            cimg_forX(permutations,x) tmp(x) = U(permutations(x),k);
            cimg_std::memcpy(U.ptr(0,k),tmp.data,sizeof(t)*width);
          }
          { cimg_forY(V,k) {
            cimg_forX(permutations,x) tmp(x) = V(permutations(x),k);
            cimg_std::memcpy(V.ptr(0,k),tmp.data,sizeof(t)*width);
          }}
        }
      }
    return *this;
    }

    //! Compute the SVD of a general matrix.
    template<typename t>
    const CImg<T>& SVD(CImgList<t>& USV) const {
      if (USV.size<3) USV.assign(3);
      return SVD(USV[0],USV[1],USV[2]);
    }

    //! Compute the SVD of a general matrix.
    CImgList<Tfloat> get_SVD(const bool sorting=true) const {
      CImgList<Tfloat> res(3);
      SVD(res[0],res[1],res[2],sorting);
      return res;
    }

    // INNER ROUTINE : Compute the LU decomposition of a permuted matrix (c.f. numerical recipies)
    template<typename t>
    CImg<T>& _LU(CImg<t>& indx, bool& d) {
      const int N = dimx();
      int imax = 0;
      CImg<Tfloat> vv(N);
      indx.assign(N);
      d = true;
      cimg_forX(*this,i) {
        Tfloat vmax = 0;
        cimg_forX(*this,j) {
          const Tfloat tmp = cimg::abs((*this)(j,i));
          if (tmp>vmax) vmax = tmp;
        }
        if (vmax==0) { indx.fill(0); return fill(0); }
        vv[i] = 1/vmax;
      }
      cimg_forX(*this,j) {
        for (int i=0; i<j; ++i) {
          Tfloat sum=(*this)(j,i);
          for (int k=0; k<i; ++k) sum-=(*this)(k,i)*(*this)(j,k);
          (*this)(j,i) = (T)sum;
        }
        Tfloat vmax = 0;
        { for (int i=j; i<dimx(); ++i) {
          Tfloat sum=(*this)(j,i);
          for (int k=0; k<j; ++k) sum-=(*this)(k,i)*(*this)(j,k);
          (*this)(j,i) = (T)sum;
          const Tfloat tmp = vv[i]*cimg::abs(sum);
          if (tmp>=vmax) { vmax=tmp; imax=i; }
        }}
        if (j!=imax) {
          cimg_forX(*this,k) cimg::swap((*this)(k,imax),(*this)(k,j));
          d =!d;
          vv[imax] = vv[j];
        }
        indx[j] = (t)imax;
        if ((*this)(j,j)==0) (*this)(j,j) = (T)1e-20;
        if (j<N) {
          const Tfloat tmp = 1/(Tfloat)(*this)(j,j);
          for (int i=j+1; i<N; ++i) (*this)(j,i) = (T)((*this)(j,i)*tmp);
        }
      }
      return *this;
    }

    //! Compute the eigenvalues and eigenvectors of a matrix.
    template<typename t>
    const CImg<T>& eigen(CImg<t>& val, CImg<t> &vec) const {
      if (is_empty()) { val.assign(); vec.assign(); }
      else {
        if (width!=height || depth>1 || dim>1)
          throw CImgInstanceException("CImg<%s>::eigen() : Instance object (%u,%u,%u,%u,%p) is empty.",
                                      pixel_type(),width,height,depth,dim,data);
        if (val.size()<width) val.assign(1,width);
        if (vec.size()<width*width) vec.assign(width,width);
        switch (width) {
        case 1 : { val[0]=(t)(*this)[0]; vec[0]=(t)1; } break;
        case 2 : {
          const double a = (*this)[0], b = (*this)[1], c = (*this)[2], d = (*this)[3], e = a+d;
          double f = e*e-4*(a*d-b*c);
          if (f<0)
            cimg::warn("CImg<%s>::eigen() : Complex eigenvalues",
                       pixel_type());
          f = cimg_std::sqrt(f);
          const double l1 = 0.5*(e-f), l2 = 0.5*(e+f);
          const double theta1 = cimg_std::atan2(l2-a,b), theta2 = cimg_std::atan2(l1-a,b);
          val[0]=(t)l2;
          val[1]=(t)l1;
          vec(0,0) = (t)cimg_std::cos(theta1);
          vec(0,1) = (t)cimg_std::sin(theta1);
          vec(1,0) = (t)cimg_std::cos(theta2);
          vec(1,1) = (t)cimg_std::sin(theta2);
        } break;
        default :
          throw CImgInstanceException("CImg<%s>::eigen() : Eigenvalues computation of general matrices is limited"
                                      "to 2x2 matrices (given is %ux%u)",
                                      pixel_type(),width,height);
        }
      }
      return *this;
    }

    //! Compute the eigenvalues and eigenvectors of a matrix.
    CImgList<Tfloat> get_eigen() const {
      CImgList<Tfloat> res(2);
      eigen(res[0],res[1]);
      return res;
    }

    //! Compute the eigenvalues and eigenvectors of a symmetric matrix.
    template<typename t>
    const CImg<T>& symmetric_eigen(CImg<t>& val, CImg<t>& vec) const {
      if (is_empty()) { val.assign(); vec.assign(); }
      else {
#ifdef cimg_use_lapack
        char JOB = 'V', UPLO = 'U';
        int N = width, LWORK = 4*N, INFO;
        Tfloat
          *lapA = new Tfloat[N*N],
          *lapW = new Tfloat[N],
          *WORK = new Tfloat[LWORK];
        cimg_forXY(*this,k,l) lapA[k*N+l] = (Tfloat)((*this)(k,l));
        cimg::syev(JOB,UPLO,N,lapA,lapW,WORK,LWORK,INFO);
        if (INFO)
          cimg::warn("CImg<%s>::symmetric_eigen() : LAPACK library function dsyev_() returned error code %d.",
                     pixel_type(),INFO);
        val.assign(1,N);
        vec.assign(N,N);
        if (!INFO) {
          cimg_forY(val,i) val(i) = (T)lapW[N-1-i];
          cimg_forXY(vec,k,l) vec(k,l) = (T)(lapA[(N-1-k)*N+l]);
        } else { val.fill(0); vec.fill(0); }
        delete[] lapA; delete[] lapW; delete[] WORK;
#else
        if (width!=height || depth>1 || dim>1)
          throw CImgInstanceException("CImg<%s>::eigen() : Instance object (%u,%u,%u,%u,%p) is empty.",
                                      pixel_type(),width,height,depth,dim,data);
        val.assign(1,width);
        if (vec.data) vec.assign(width,width);
        if (width<3) return eigen(val,vec);
        CImg<t> V(width,width);
        SVD(vec,val,V,false);
        bool ambiguous = false;
        float eig = 0;
        cimg_forY(val,p) {       // check for ambiguous cases.
          if (val[p]>eig) eig = (float)val[p];
          t scal = 0;
          cimg_forY(vec,y) scal+=vec(p,y)*V(p,y);
          if (cimg::abs(scal)<0.9f) ambiguous = true;
          if (scal<0) val[p] = -val[p];
        }
        if (ambiguous) {
          (eig*=2)++;
          SVD(vec,val,V,false,40,eig);
          val-=eig;
        }
        CImg<intT> permutations(width);  // sort eigenvalues in decreasing order
        CImg<t> tmp(width);
        val.sort(permutations,false);
        cimg_forY(vec,k) {
          cimg_forX(permutations,x) tmp(x) = vec(permutations(x),k);
          cimg_std::memcpy(vec.ptr(0,k),tmp.data,sizeof(t)*width);
        }
#endif
      }
      return *this;
    }

    //! Compute the eigenvalues and eigenvectors of a symmetric matrix.
    CImgList<Tfloat> get_symmetric_eigen() const {
      CImgList<Tfloat> res(2);
      symmetric_eigen(res[0],res[1]);
      return res;
    }

    //@}
    //-------------------
    //
    //! \name Display
    //@{
    //-------------------

    //! Display an image into a CImgDisplay window.
    const CImg<T>& display(CImgDisplay& disp) const {
      disp.display(*this);
      return *this;
    }

    //! Display an image in a window with a title \p title, and wait a 'is_closed' or 'keyboard' event.\n
    const CImg<T>& display(CImgDisplay &disp, const bool display_info) const {
      return _display(disp,0,display_info);
    }

    //! Display an image in a window with a title \p title, and wait a 'is_closed' or 'keyboard' event.\n
    const CImg<T>& display(const char *const title=0, const bool display_info=true) const {
      CImgDisplay disp;
      return _display(disp,title,display_info);
    }

    const CImg<T>& _display(CImgDisplay &disp, const char *const title, const bool display_info) const {
      if (is_empty())
        throw CImgInstanceException("CImg<%s>::display() : Instance image (%u,%u,%u,%u,%p) is empty.",
                                    pixel_type(),width,height,depth,dim,data);
      unsigned int oldw = 0, oldh = 0, XYZ[3], key = 0, mkey = 0;
      int x0 = 0, y0 = 0, z0 = 0, x1 = dimx()-1, y1 = dimy()-1, z1 = dimz()-1;
      float frametiming = 5;

      char ntitle[256] = { 0 };
      if (!disp) {
        if (!title) cimg_std::sprintf(ntitle,"CImg<%s>",pixel_type());
        disp.assign(cimg_fitscreen(width,height,depth),title?title:ntitle,1);
      }
      cimg_std::strncpy(ntitle,disp.title,255);
      if (display_info) print(ntitle);

      CImg<T> zoom;
      for (bool reset_view = true, resize_disp = false; !key && !disp.is_closed; ) {
        if (reset_view) {
          XYZ[0] = (x0 + x1)/2; XYZ[1] = (y0 + y1)/2; XYZ[2] = (z0 + z1)/2;
          x0 = 0; y0 = 0; z0 = 0; x1 = width-1; y1 = height-1; z1 = depth-1;
          oldw = disp.width; oldh = disp.height;
          reset_view = false;
        }
        if (!x0 && !y0 && !z0 && x1==dimx()-1 && y1==dimy()-1 && z1==dimz()-1) zoom.assign();
        else zoom = get_crop(x0,y0,z0,x1,y1,z1);

        const unsigned int
          dx = 1 + x1 - x0, dy = 1 + y1 - y0, dz = 1 + z1 - z0,
          tw = dx + (dz>1?dz:0), th = dy + (dz>1?dz:0);
        if (resize_disp) {
          const unsigned int
            ttw = tw*disp.width/oldw, tth = th*disp.height/oldh,
            dM = cimg::max(ttw,tth), diM = cimg::max(disp.width,disp.height),
            imgw = cimg::max(16U,ttw*diM/dM), imgh = cimg::max(16U,tth*diM/dM);
          disp.normalscreen().resize(cimg_fitscreen(imgw,imgh,1),false);
          resize_disp = false;
        }
        oldw = tw; oldh = th;

        bool
          go_up = false, go_down = false, go_left = false, go_right = false,
          go_inc = false, go_dec = false, go_in = false, go_out = false,
          go_in_center = false;
        const CImg<T>& visu = zoom?zoom:*this;
        const CImg<intT> selection = visu._get_select(disp,0,2,XYZ,0,x0,y0,z0);
        if (disp.wheel) {
          if (disp.is_keyCTRLLEFT) { if (!mkey || mkey==1) go_out = !(go_in = disp.wheel>0); go_in_center = false; mkey = 1; }
          else if (disp.is_keySHIFTLEFT) { if (!mkey || mkey==2) go_right = !(go_left = disp.wheel>0); mkey = 2; }
          else if (disp.is_keyALT || depth==1) { if (!mkey || mkey==3) go_down = !(go_up = disp.wheel>0); mkey = 3; }
          else mkey = 0;
          disp.wheel = 0;
        } else mkey = 0;
        const int
          sx0 = selection(0), sy0 = selection(1), sz0 = selection(2),
          sx1 = selection(3), sy1 = selection(4), sz1 = selection(5);
        if (sx0>=0 && sy0>=0 && sz0>=0 && sx1>=0 && sy1>=0 && sz1>=0) {
          x1 = x0 + sx1; y1 = y0 + sy1; z1 = z0 + sz1; x0+=sx0; y0+=sy0; z0+=sz0;
          if (sx0==sx1 && sy0==sy1 && sz0==sz1) reset_view = true;
          resize_disp = true;
        } else switch (key = disp.key) {
        case 0 : case cimg::keyCTRLLEFT : case cimg::keyPAD5 : case cimg::keySHIFTLEFT : case cimg::keyALT : disp.key = key = 0; break;
        case cimg::keyP : if (visu.depth>1 && disp.is_keyCTRLLEFT) { // Special mode : play stack of frames
          const unsigned int
            w1 = visu.width*disp.width/(visu.width+(visu.depth>1?visu.depth:0)),
            h1 = visu.height*disp.height/(visu.height+(visu.depth>1?visu.depth:0));
          disp.resize(cimg_fitscreen(w1,h1,1),false).key = disp.wheel = key = 0;
          for (unsigned int timer = 0; !key && !disp.is_closed && !disp.button; ) {
            if (disp.is_resized) disp.resize();
            if (!timer) {
              visu.get_slice(XYZ[2]).display(disp.set_title("%s | z=%d",ntitle,XYZ[2]));
              if (++XYZ[2]>=visu.depth) XYZ[2] = 0;
            }
            if (++timer>(unsigned int)frametiming) timer = 0;
            if (disp.wheel) { frametiming-=disp.wheel/3.0f; disp.wheel = 0; }
            switch (key = disp.key) {
            case 0 : case cimg::keyCTRLLEFT : disp.key = key = 0; break;
            case cimg::keyPAGEUP : frametiming-=0.3f; key = 0; break;
            case cimg::keyPAGEDOWN : frametiming+=0.3f; key = 0; break;
            case cimg::keyD : if (disp.is_keyCTRLLEFT) {
              disp.normalscreen().resize(CImgDisplay::_fitscreen(3*disp.width/2,3*disp.height/2,1,128,-100,false),
                                         CImgDisplay::_fitscreen(3*disp.width/2,3*disp.height/2,1,128,-100,true),false);
              disp.key = key = 0;
            } break;
            case cimg::keyC : if (disp.is_keyCTRLLEFT) {
              disp.normalscreen().resize(cimg_fitscreen(2*disp.width/3,2*disp.height/3,1),false);
              disp.key = key = 0;
            } break;
            case cimg::keyR : if (disp.is_keyCTRLLEFT) {
              disp.normalscreen().resize(cimg_fitscreen(width,height,depth),false);
              disp.key = key = 0;
            } break;
            case cimg::keyF : if (disp.is_keyCTRLLEFT) {
              disp.resize(disp.screen_dimx(),disp.screen_dimy()).toggle_fullscreen();
              disp.key = key = 0;
            } break;
            }
            frametiming = frametiming<1?1:(frametiming>39?39:frametiming);
            disp.wait(20);
          }
          const unsigned int
            w2 = (visu.width + (visu.depth>1?visu.depth:0))*disp.width/visu.width,
            h2 = (visu.height + (visu.depth>1?visu.depth:0))*disp.height/visu.height;
          disp.resize(cimg_fitscreen(w2,h2,1),false).set_title(ntitle);
          key = disp.key = disp.button = disp.wheel = 0;
        } break;
        case cimg::keyHOME : case cimg::keyBACKSPACE : reset_view = resize_disp = true; key = 0; break;
        case cimg::keyPADADD : go_in = true; go_in_center = true; key = 0; break;
        case cimg::keyPADSUB : go_out = true; key = 0; break;
        case cimg::keyARROWLEFT : case cimg::keyPAD4: go_left = true; key = 0; break;
        case cimg::keyARROWRIGHT : case cimg::keyPAD6: go_right = true; key = 0; break;
        case cimg::keyARROWUP : case cimg::keyPAD8: go_up = true; key = 0; break;
        case cimg::keyARROWDOWN : case cimg::keyPAD2: go_down = true; key = 0; break;
        case cimg::keyPAD7 : go_up = go_left = true; key = 0; break;
        case cimg::keyPAD9 : go_up = go_right = true; key = 0; break;
        case cimg::keyPAD1 : go_down = go_left = true; key = 0; break;
        case cimg::keyPAD3 : go_down = go_right = true; key = 0; break;
        case cimg::keyPAGEUP : go_inc = true; key = 0; break;
        case cimg::keyPAGEDOWN : go_dec = true; key = 0; break;
        }
        if (go_in) {
          const int
            mx = go_in_center?disp.dimx()/2:disp.mouse_x,
            my = go_in_center?disp.dimy()/2:disp.mouse_y,
            mX = mx*(width+(depth>1?depth:0))/disp.width,
            mY = my*(height+(depth>1?depth:0))/disp.height;
          int X = XYZ[0], Y = XYZ[1], Z = XYZ[2];
          if (mX<dimx() && mY<dimy())  { X = x0 + mX*(1+x1-x0)/width; Y = y0 + mY*(1+y1-y0)/height; Z = XYZ[2]; }
          if (mX<dimx() && mY>=dimy()) { X = x0 + mX*(1+x1-x0)/width; Z = z0 + (mY-height)*(1+z1-z0)/depth; Y = XYZ[1]; }
          if (mX>=dimx() && mY<dimy()) { Y = y0 + mY*(1+y1-y0)/height; Z = z0 + (mX-width)*(1+z1-z0)/depth; X = XYZ[0]; }
          if (x1-x0>4) { x0 = X - 7*(X-x0)/8; x1 = X + 7*(x1-X)/8; }
          if (y1-y0>4) { y0 = Y - 7*(Y-y0)/8; y1 = Y + 7*(y1-Y)/8; }
          if (z1-z0>4) { z0 = Z - 7*(Z-z0)/8; z1 = Z + 7*(z1-Z)/8; }
        }
        if (go_out) {
          const int
            deltax = (x1-x0)/8, deltay = (y1-y0)/8, deltaz = (z1-z0)/8,
            ndeltax = deltax?deltax:(width>1?1:0),
            ndeltay = deltay?deltay:(height>1?1:0),
            ndeltaz = deltaz?deltaz:(depth>1?1:0);
          x0-=ndeltax; y0-=ndeltay; z0-=ndeltaz;
          x1+=ndeltax; y1+=ndeltay; z1+=ndeltaz;
          if (x0<0) { x1-=x0; x0 = 0; if (x1>=dimx()) x1 = dimx()-1; }
          if (y0<0) { y1-=y0; y0 = 0; if (y1>=dimy()) y1 = dimy()-1; }
          if (z0<0) { z1-=z0; z0 = 0; if (z1>=dimz()) z1 = dimz()-1; }
          if (x1>=dimx()) { x0-=(x1-dimx()+1); x1 = dimx()-1; if (x0<0) x0 = 0; }
          if (y1>=dimy()) { y0-=(y1-dimy()+1); y1 = dimy()-1; if (y0<0) y0 = 0; }
          if (z1>=dimz()) { z0-=(z1-dimz()+1); z1 = dimz()-1; if (z0<0) z0 = 0; }
        }
        if (go_left) {
          const int delta = (x1-x0)/5, ndelta = delta?delta:(width>1?1:0);
          if (x0-ndelta>=0) { x0-=ndelta; x1-=ndelta; }
          else { x1-=x0; x0 = 0; }
        }
        if (go_right) {
          const int delta = (x1-x0)/5, ndelta = delta?delta:(width>1?1:0);
          if (x1+ndelta<dimx()) { x0+=ndelta; x1+=ndelta; }
          else { x0+=(dimx()-1-x1); x1 = dimx()-1; }
        }
        if (go_up) {
          const int delta = (y1-y0)/5, ndelta = delta?delta:(height>1?1:0);
          if (y0-ndelta>=0) { y0-=ndelta; y1-=ndelta; }
          else { y1-=y0; y0 = 0; }
        }
        if (go_down) {
          const int delta = (y1-y0)/5, ndelta = delta?delta:(height>1?1:0);
          if (y1+ndelta<dimy()) { y0+=ndelta; y1+=ndelta; }
          else { y0+=(dimy()-1-y1); y1 = dimy()-1; }
        }
        if (go_inc) {
          const int delta = (z1-z0)/5, ndelta = delta?delta:(depth>1?1:0);
          if (z0-ndelta>=0) { z0-=ndelta; z1-=ndelta; }
          else { z1-=z0; z0 = 0; }
        }
        if (go_dec) {
          const int delta = (z1-z0)/5, ndelta = delta?delta:(depth>1?1:0);
          if (z1+ndelta<dimz()) { z0+=ndelta; z1+=ndelta; }
          else { z0+=(depth-1-z1); z1 = depth-1; }
        }
      }
      disp.key = key;
      return *this;
    }

    //! Simple interface to select a shape from an image.
    /**
       \param selection  Array of 6 values containing the selection result
       \param coords_type Determine shape type to select (0=point, 1=vector, 2=rectangle, 3=circle)
       \param disp       Display window used to make the selection
       \param XYZ        Initial XYZ position (for volumetric images only)
       \param color      Color of the shape selector.
    **/
    CImg<T>& select(CImgDisplay &disp,
                    const int select_type=2, unsigned int *const XYZ=0,
                    const unsigned char *const color=0) {
      return get_select(disp,select_type,XYZ,color).transfer_to(*this);
    }

    //! Simple interface to select a shape from an image.
    CImg<T>& select(const char *const title,
                    const int select_type=2, unsigned int *const XYZ=0,
                    const unsigned char *const color=0) {
      return get_select(title,select_type,XYZ,color).transfer_to(*this);
    }

    //! Simple interface to select a shape from an image.
    CImg<intT> get_select(CImgDisplay &disp,
                          const int select_type=2, unsigned int *const XYZ=0,
                          const unsigned char *const color=0) const {
      return _get_select(disp,0,select_type,XYZ,color,0,0,0);
    }

    //! Simple interface to select a shape from an image.
    CImg<intT> get_select(const char *const title,
                          const int select_type=2, unsigned int *const XYZ=0,
                          const unsigned char *const color=0) const {
      CImgDisplay disp;
      return _get_select(disp,title,select_type,XYZ,color,0,0,0);
    }

    CImg<intT> _get_select(CImgDisplay &disp, const char *const title,
                           const int coords_type, unsigned int *const XYZ,
                           const unsigned char *const color,
                           const int origX, const int origY, const int origZ) const {
      if (is_empty())
        throw CImgInstanceException("CImg<%s>::select() : Instance image (%u,%u,%u,%u,%p) is empty.",
                                    pixel_type(),width,height,depth,dim,data);
      if (!disp) {
        char ntitle[64] = { 0 }; if (!title) { cimg_std::sprintf(ntitle,"CImg<%s>",pixel_type()); }
        disp.assign(cimg_fitscreen(width,height,depth),title?title:ntitle,1);
      }

      const unsigned int
        old_normalization = disp.normalization,
        hatch = 0x55555555;

      bool old_is_resized = disp.is_resized;
      disp.normalization = 0;
      disp.show().key = 0;

      unsigned char foreground_color[] = { 255,255,105 }, background_color[] = { 0,0,0 };
      if (color) cimg_std::memcpy(foreground_color,color,sizeof(unsigned char)*cimg::min(3,dimv()));

      int area = 0, clicked_area = 0, phase = 0,
        X0 = (int)((XYZ?XYZ[0]:width/2)%width), Y0 = (int)((XYZ?XYZ[1]:height/2)%height), Z0 = (int)((XYZ?XYZ[2]:depth/2)%depth),
        X1 =-1, Y1 = -1, Z1 = -1,
        X = -1, Y = -1, Z = -1,
        oX = X, oY = Y, oZ = Z;
      unsigned int old_button = 0, key = 0;

      bool shape_selected = false, text_down = false;
      CImg<ucharT> visu, visu0;
      char text[1024] = { 0 };

      while (!key && !disp.is_closed && !shape_selected) {

        // Handle mouse motion and selection
        oX = X; oY = Y; oZ = Z;
        int mx = disp.mouse_x, my = disp.mouse_y;
        const int mX = mx*(width+(depth>1?depth:0))/disp.width, mY = my*(height+(depth>1?depth:0))/disp.height;

        area = 0;
        if (mX<dimx() && mY<dimy())  { area = 1; X = mX; Y = mY; Z = phase?Z1:Z0; }
        if (mX<dimx() && mY>=dimy()) { area = 2; X = mX; Z = mY-height; Y = phase?Y1:Y0; }
        if (mX>=dimx() && mY<dimy()) { area = 3; Y = mY; Z = mX-width; X = phase?X1:X0; }

        switch (key = disp.key) {
        case 0 : case cimg::keyCTRLLEFT : disp.key = key = 0; break;
        case cimg::keyPAGEUP : if (disp.is_keyCTRLLEFT) { ++disp.wheel; key = 0; } break;
        case cimg::keyPAGEDOWN : if (disp.is_keyCTRLLEFT) { --disp.wheel; key = 0; } break;
        case cimg::keyD : if (disp.is_keyCTRLLEFT) {
          disp.normalscreen().resize(CImgDisplay::_fitscreen(3*disp.width/2,3*disp.height/2,1,128,-100,false),
                                     CImgDisplay::_fitscreen(3*disp.width/2,3*disp.height/2,1,128,-100,true),false).is_resized = true;
          disp.key = key = 0;
        } break;
        case cimg::keyC : if (disp.is_keyCTRLLEFT) {
          disp.normalscreen().resize(cimg_fitscreen(2*disp.width/3,2*disp.height/3,1),false).is_resized = true;
          disp.key = key = 0; visu0.assign();
        } break;
        case cimg::keyR : if (disp.is_keyCTRLLEFT) {
          disp.normalscreen().resize(cimg_fitscreen(width,height,depth),false).is_resized = true;
          disp.key = key = 0; visu0.assign();
        } break;
        case cimg::keyF : if (disp.is_keyCTRLLEFT) {
          disp.resize(disp.screen_dimx(),disp.screen_dimy(),false).toggle_fullscreen().is_resized = true;
          disp.key = key = 0; visu0.assign();
        } break;
        case cimg::keyS : if (disp.is_keyCTRLLEFT) {
          static unsigned int snap_number = 0;
          char filename[32] = { 0 };
          cimg_std::FILE *file;
          do {
            cimg_std::sprintf(filename,"CImg_%.4u.bmp",snap_number++);
            if ((file=cimg_std::fopen(filename,"r"))!=0) cimg_std::fclose(file);
          } while (file);
          if (visu0) {
            visu.draw_text(2,2,"Saving snapshot...",foreground_color,background_color,0.8f,11).display(disp);
            visu0.save(filename);
            visu.draw_text(2,2,"Snapshot '%s' saved.",foreground_color,background_color,0.8f,11,filename).display(disp);
          }
          disp.key = key = 0;
        } break;
        case cimg::keyO : if (disp.is_keyCTRLLEFT) {
          static unsigned int snap_number = 0;
          char filename[32] = { 0 };
          cimg_std::FILE *file;
          do {
            cimg_std::sprintf(filename,"CImg_%.4u.cimg",snap_number++);
            if ((file=cimg_std::fopen(filename,"r"))!=0) cimg_std::fclose(file);
          } while (file);
          visu.draw_text(2,2,"Saving instance...",foreground_color,background_color,0.8f,11).display(disp);
          save(filename);
          visu.draw_text(2,2,"Instance '%s' saved.",foreground_color,background_color,0.8f,11,filename).display(disp);
          disp.key = key = 0;
        } break;
        }

        if (!area) mx = my = X = Y = Z = -1;
        else {
          if (disp.button&1 && phase<2) { X1 = X; Y1 = Y; Z1 = Z; }
          if (!(disp.button&1) && phase>=2) {
            switch (clicked_area) {
            case 1 : Z1 = Z; break;
            case 2 : Y1 = Y; break;
            case 3 : X1 = X; break;
            }
          }
          if (disp.button&2) { if (phase) { X1 = X; Y1 = Y; Z1 = Z; } else { X0 = X; Y0 = Y; Z0 = Z; } }
          if (disp.button&4) { oX = X = X0; oY = Y = Y0; oZ = Z = Z0; phase = 0; visu.assign(); }
          if (disp.wheel) {
            if (depth>1 && !disp.is_keyCTRLLEFT && !disp.is_keySHIFTLEFT && !disp.is_keyALT) {
              switch (area) {
              case 1 : if (phase) Z = (Z1+=disp.wheel); else Z = (Z0+=disp.wheel); break;
              case 2 : if (phase) Y = (Y1+=disp.wheel); else Y = (Y0+=disp.wheel); break;
              case 3 : if (phase) X = (X1+=disp.wheel); else X = (X0+=disp.wheel); break;
              }
              disp.wheel = 0;
            } else key = ~0U;
          }
          if ((disp.button&1)!=old_button) {
            switch (phase++) {
            case 0 : X0 = X1 = X; Y0 = Y1 = Y; Z0 = Z1 = Z; clicked_area = area; break;
            case 1 : X1 = X; Y1 = Y; Z1 = Z; break;
            }
            old_button = disp.button&1;
          }
          if (depth>1 && (X!=oX || Y!=oY || Z!=oZ)) visu0.assign();
        }

        if (phase) {
          if (!coords_type) shape_selected = phase?true:false;
          else {
            if (depth>1) shape_selected = (phase==3)?true:false;
            else shape_selected = (phase==2)?true:false;
          }
        }

        if (X0<0) X0 = 0; if (X0>=dimx()) X0 = dimx()-1; if (Y0<0) Y0 = 0; if (Y0>=dimy()) Y0 = dimy()-1;
        if (Z0<0) Z0 = 0; if (Z0>=dimz()) Z0 = dimz()-1;
        if (X1<1) X1 = 0; if (X1>=dimx()) X1 = dimx()-1; if (Y1<0) Y1 = 0; if (Y1>=dimy()) Y1 = dimy()-1;
        if (Z1<0) Z1 = 0; if (Z1>=dimz()) Z1 = dimz()-1;

        // Draw visualization image on the display
        if (oX!=X || oY!=Y || oZ!=Z || !visu0) {
          if (!visu0) {
            CImg<Tuchar> tmp, tmp0;
            if (depth!=1) {
              tmp0 = (!phase)?get_projections2d(X0,Y0,Z0):get_projections2d(X1,Y1,Z1);
              tmp = tmp0.get_channels(0,cimg::min(2U,dim-1));
            } else tmp = get_channels(0,cimg::min(2U,dim-1));
            switch (old_normalization) {
            case 0 : visu0 = tmp; break;
            case 3 :
              if (cimg::type<T>::is_float()) visu0 = tmp.normalize(0,(T)255);
              else {
                const float m = (float)cimg::type<T>::min(), M = (float)cimg::type<T>::max();
                visu0.assign(tmp.width,tmp.height,1,tmp.dim);
                unsigned char *ptrd = visu0.end();
                cimg_for(tmp,ptrs,Tuchar) *(--ptrd) = (unsigned char)((*ptrs-m)*255.0f/(M-m));
              } break;
            default : visu0 = tmp.normalize(0,255);
            }
            visu0.resize(disp);
          }
          visu = visu0;
          if (!color) {
            if (visu.mean()<200) {
              foreground_color[0] = foreground_color[1] = foreground_color[2] = 255;
              background_color[0] = background_color[1] = background_color[2] = 0;
            } else {
              foreground_color[0] = foreground_color[1] = foreground_color[2] = 0;
              background_color[0] = background_color[1] = background_color[2] = 255;
            }
          }

          const int d = (depth>1)?depth:0;
          if (phase) switch (coords_type) {
          case 1 : {
            const int
              x0 = (int)((X0+0.5f)*disp.width/(width+d)),
              y0 = (int)((Y0+0.5f)*disp.height/(height+d)),
              x1 = (int)((X1+0.5f)*disp.width/(width+d)),
              y1 = (int)((Y1+0.5f)*disp.height/(height+d));
            visu.draw_arrow(x0,y0,x1,y1,foreground_color,0.6f,30,5,hatch);
            if (d) {
              const int
                zx0 = (int)((width+Z0+0.5f)*disp.width/(width+d)),
                zx1 = (int)((width+Z1+0.5f)*disp.width/(width+d)),
                zy0 = (int)((height+Z0+0.5f)*disp.height/(height+d)),
                zy1 = (int)((height+Z1+0.5f)*disp.height/(height+d));
              visu.draw_arrow(zx0,y0,zx1,y1,foreground_color,0.6f,30,5,hatch).
                draw_arrow(x0,zy0,x1,zy1,foreground_color,0.6f,30,5,hatch);
            }
          } break;
          case 2 : {
            const int
              x0 = (X0<X1?X0:X1)*disp.width/(width+d), y0 = (Y0<Y1?Y0:Y1)*disp.height/(height+d),
              x1 = ((X0<X1?X1:X0)+1)*disp.width/(width+d)-1, y1 = ((Y0<Y1?Y1:Y0)+1)*disp.height/(height+d)-1;
            visu.draw_rectangle(x0,y0,x1,y1,foreground_color,0.2f).draw_rectangle(x0,y0,x1,y1,foreground_color,0.6f,hatch);
            if (d) {
              const int
                zx0 = (int)((width+(Z0<Z1?Z0:Z1))*disp.width/(width+d)),
                zy0 = (int)((height+(Z0<Z1?Z0:Z1))*disp.height/(height+d)),
                zx1 = (int)((width+(Z0<Z1?Z1:Z0)+1)*disp.width/(width+d))-1,
                zy1 = (int)((height+(Z0<Z1?Z1:Z0)+1)*disp.height/(height+d))-1;
              visu.draw_rectangle(zx0,y0,zx1,y1,foreground_color,0.2f).draw_rectangle(zx0,y0,zx1,y1,foreground_color,0.6f,hatch);
              visu.draw_rectangle(x0,zy0,x1,zy1,foreground_color,0.2f).draw_rectangle(x0,zy0,x1,zy1,foreground_color,0.6f,hatch);
            }
          } break;
          case 3 : {
            const int
              x0 = X0*disp.width/(width+d),
              y0 = Y0*disp.height/(height+d),
              x1 = X1*disp.width/(width+d)-1,
              y1 = Y1*disp.height/(height+d)-1;
            visu.draw_ellipse(x0,y0,(float)(x1-x0),(float)(y1-y0),1,0,foreground_color,0.2f).
              draw_ellipse(x0,y0,(float)(x1-x0),(float)(y1-y0),1,0,foreground_color,0.6f,hatch);
            if (d) {
              const int
                zx0 = (int)((width+Z0)*disp.width/(width+d)),
                zy0 = (int)((height+Z0)*disp.height/(height+d)),
                zx1 = (int)((width+Z1+1)*disp.width/(width+d))-1,
                zy1 = (int)((height+Z1+1)*disp.height/(height+d))-1;
              visu.draw_ellipse(zx0,y0,(float)(zx1-zx0),(float)(y1-y0),1,0,foreground_color,0.2f).
                draw_ellipse(zx0,y0,(float)(zx1-zx0),(float)(y1-y0),1,0,foreground_color,0.6f,hatch).
                draw_ellipse(x0,zy0,(float)(x1-x0),(float)(zy1-zy0),1,0,foreground_color,0.2f).
                draw_ellipse(x0,zy0,(float)(x1-x0),(float)(zy1-zy0),1,0,foreground_color,0.6f,hatch);
            }
          } break;
          } else {
            const int
              x0 = X*disp.width/(width+d),
              y0 = Y*disp.height/(height+d),
              x1 = (X+1)*disp.width/(width+d)-1,
              y1 = (Y+1)*disp.height/(height+d)-1;
            if (x1-x0>=4 && y1-y0>=4) visu.draw_rectangle(x0,y0,x1,y1,foreground_color,0.4f,~0U);
          }

          if (my<12) text_down = true;
          if (my>=visu.dimy()-11) text_down = false;
          if (!coords_type || !phase) {
            if (X>=0 && Y>=0 && Z>=0 && X<dimx() && Y<dimy() && Z<dimz()) {
              if (depth>1) cimg_std::sprintf(text,"Point (%d,%d,%d) = [ ",origX+X,origY+Y,origZ+Z);
              else cimg_std::sprintf(text,"Point (%d,%d) = [ ",origX+X,origY+Y);
              char *ctext = text + cimg::strlen(text), *const ltext = text + 512;
              for (unsigned int k=0; k<dim && ctext<ltext; ++k) {
                cimg_std::sprintf(ctext,cimg::type<T>::format(),cimg::type<T>::format((*this)(X,Y,Z,k)));
                ctext = text + cimg::strlen(text);
                *(ctext++) = ' '; *ctext = '\0';
              }
              cimg_std::sprintf(text + cimg::strlen(text),"]");
            }
          } else switch (coords_type) {
          case 1 : {
            const double dX = (double)(X0 - X1), dY = (double)(Y0 - Y1), dZ = (double)(Z0 - Z1), norm = cimg_std::sqrt(dX*dX+dY*dY+dZ*dZ);
            if (depth>1) cimg_std::sprintf(text,"Vect (%d,%d,%d)-(%d,%d,%d), Norm = %g",
                                      origX+X0,origY+Y0,origZ+Z0,origX+X1,origY+Y1,origZ+Z1,norm);
            else cimg_std::sprintf(text,"Vect (%d,%d)-(%d,%d), Norm = %g",
                              origX+X0,origY+Y0,origX+X1,origY+Y1,norm);
          } break;
          case 2 :
            if (depth>1) cimg_std::sprintf(text,"Box (%d,%d,%d)-(%d,%d,%d), Size = (%d,%d,%d)",
                                      origX+(X0<X1?X0:X1),origY+(Y0<Y1?Y0:Y1),origZ+(Z0<Z1?Z0:Z1),
                                      origX+(X0<X1?X1:X0),origY+(Y0<Y1?Y1:Y0),origZ+(Z0<Z1?Z1:Z0),
                                      1+cimg::abs(X0-X1),1+cimg::abs(Y0-Y1),1+cimg::abs(Z0-Z1));
            else  cimg_std::sprintf(text,"Box (%d,%d)-(%d,%d), Size = (%d,%d)",
                               origX+(X0<X1?X0:X1),origY+(Y0<Y1?Y0:Y1),origX+(X0<X1?X1:X0),origY+(Y0<Y1?Y1:Y0),
                               1+cimg::abs(X0-X1),1+cimg::abs(Y0-Y1));
            break;
          default :
            if (depth>1) cimg_std::sprintf(text,"Ellipse (%d,%d,%d)-(%d,%d,%d), Radii = (%d,%d,%d)",
                                      origX+X0,origY+Y0,origZ+Z0,origX+X1,origY+Y1,origZ+Z1,
                                      1+cimg::abs(X0-X1),1+cimg::abs(Y0-Y1),1+cimg::abs(Z0-Z1));
            else  cimg_std::sprintf(text,"Ellipse (%d,%d)-(%d,%d), Radii = (%d,%d)",
                               origX+X0,origY+Y0,origX+X1,origY+Y1,1+cimg::abs(X0-X1),1+cimg::abs(Y0-Y1));

          }
          if (phase || (mx>=0 && my>=0)) visu.draw_text(0,text_down?visu.dimy()-11:0,text,foreground_color,background_color,0.7f,11);
          disp.display(visu).wait(25);
        } else if (!shape_selected) disp.wait();

        if (disp.is_resized) { disp.resize(false); old_is_resized = true; disp.is_resized = false; visu0.assign(); }
      }

      // Return result
      CImg<intT> res(1,6,1,1,-1);
      if (XYZ) { XYZ[0] = (unsigned int)X0; XYZ[1] = (unsigned int)Y0; XYZ[2] = (unsigned int)Z0; }
      if (shape_selected) {
        if (coords_type==2) {
          if (X0>X1) cimg::swap(X0,X1);
          if (Y0>Y1) cimg::swap(Y0,Y1);
          if (Z0>Z1) cimg::swap(Z0,Z1);
        }
        if (X1<0 || Y1<0 || Z1<0) X0 = Y0 = Z0 = X1 = Y1 = Z1 = -1;
        switch (coords_type) {
        case 1 :
        case 2 :  res[3] = X1; res[4] = Y1; res[5] = Z1;
        default : res[0] = X0; res[1] = Y0; res[2] = Z0;
        }
      }
      disp.button = 0;
      disp.normalization = old_normalization;
      disp.is_resized = old_is_resized;
      if (key!=~0U) disp.key = key;
      return res;
    }

    //! High-level interface for displaying a 3d object.
    template<typename tp, typename tf, typename tc, typename to>
    const CImg<T>& display_object3d(CImgDisplay& disp,
                                    const CImg<tp>& points, const CImgList<tf>& primitives,
                                    const CImgList<tc>& colors, const to& opacities,
                                    const bool centering=true,
                                    const int render_static=4, const int render_motion=1,
                                    const bool double_sided=false, const float focale=500,
                                    const float specular_light=0.2f, const float specular_shine=0.1f,
                                    const bool display_axes=true, float *const pose_matrix=0) const {
      return _display_object3d(disp,0,points,points.width,primitives,colors,opacities,centering,render_static,
                               render_motion,double_sided,focale,specular_light,specular_shine,
                               display_axes,pose_matrix);
    }

    //! High-level interface for displaying a 3d object.
    template<typename tp, typename tf, typename tc, typename to>
    const CImg<T>& display_object3d(const char *const title,
                                    const CImg<tp>& points, const CImgList<tf>& primitives,
                                    const CImgList<tc>& colors, const to& opacities,
                                    const bool centering=true,
                                    const int render_static=4, const int render_motion=1,
                                    const bool double_sided=false, const float focale=500,
                                    const float specular_light=0.2f, const float specular_shine=0.1f,
                                    const bool display_axes=true, float *const pose_matrix=0) const {
      CImgDisplay disp;
      return _display_object3d(disp,title,points,points.width,primitives,colors,opacities,centering,render_static,
                               render_motion,double_sided,focale,specular_light,specular_shine,
                               display_axes,pose_matrix);
    }

    //! High-level interface for displaying a 3d object.
    template<typename tp, typename tf, typename tc, typename to>
    const CImg<T>& display_object3d(CImgDisplay& disp,
                                    const CImgList<tp>& points, const CImgList<tf>& primitives,
                                    const CImgList<tc>& colors, const to& opacities,
                                    const bool centering=true,
                                    const int render_static=4, const int render_motion=1,
                                    const bool double_sided=false, const float focale=500,
                                    const float specular_light=0.2f, const float specular_shine=0.1f,
                                    const bool display_axes=true, float *const pose_matrix=0) const {
      return _display_object3d(disp,0,points,points.size,primitives,colors,opacities,centering,render_static,
                               render_motion,double_sided,focale,specular_light,specular_shine,
                               display_axes,pose_matrix);
    }

    //! High-level interface for displaying a 3d object.
    template<typename tp, typename tf, typename tc, typename to>
    const CImg<T>& display_object3d(const char *const title,
                                    const CImgList<tp>& points, const CImgList<tf>& primitives,
                                    const CImgList<tc>& colors, const to& opacities,
                                    const bool centering=true,
                                    const int render_static=4, const int render_motion=1,
                                    const bool double_sided=false, const float focale=500,
                                    const float specular_light=0.2f, const float specular_shine=0.1f,
                                    const bool display_axes=true, float *const pose_matrix=0) const {
      CImgDisplay disp;
      return _display_object3d(disp,title,points,points.size,primitives,colors,opacities,centering,render_static,
                               render_motion,double_sided,focale,specular_light,specular_shine,
                               display_axes,pose_matrix);
    }

   //! High-level interface for displaying a 3d object.
    template<typename tp, typename tf, typename tc>
    const CImg<T>& display_object3d(CImgDisplay &disp,
                                    const tp& points, const CImgList<tf>& primitives,
                                    const CImgList<tc>& colors,
                                    const bool centering=true,
                                    const int render_static=4, const int render_motion=1,
                                    const bool double_sided=false, const float focale=500,
                                    const float specular_light=0.2f, const float specular_shine=0.1f,
                                    const bool display_axes=true, float *const pose_matrix=0) const {
      return display_object3d(disp,points,primitives,colors,CImg<floatT>(),centering,
                              render_static,render_motion,double_sided,focale,specular_light,specular_shine,
                              display_axes,pose_matrix);
    }

    //! High-level interface for displaying a 3d object.
    template<typename tp, typename tf, typename tc>
    const CImg<T>& display_object3d(const char *const title,
                                    const tp& points, const CImgList<tf>& primitives,
                                    const CImgList<tc>& colors,
                                    const bool centering=true,
                                    const int render_static=4, const int render_motion=1,
                                    const bool double_sided=false, const float focale=500,
                                    const float specular_light=0.2f, const float specular_shine=0.1f,
                                    const bool display_axes=true, float *const pose_matrix=0) const {
      return display_object3d(title,points,primitives,colors,CImg<floatT>(),centering,
                              render_static,render_motion,double_sided,focale,specular_light,specular_shine,
                              display_axes,pose_matrix);
    }

    //! High-level interface for displaying a 3d object.
    template<typename tp, typename tf>
    const CImg<T>& display_object3d(CImgDisplay &disp,
                                    const tp& points, const CImgList<tf>& primitives,
                                    const bool centering=true,
                                    const int render_static=4, const int render_motion=1,
                                    const bool double_sided=false, const float focale=500,
                                    const float specular_light=0.2f, const float specular_shine=0.1f,
                                    const bool display_axes=true, float *const pose_matrix=0) const {
      return display_object3d(disp,points,primitives,CImgList<T>(),centering,
                              render_static,render_motion,double_sided,focale,specular_light,specular_shine,
                              display_axes,pose_matrix);
    }

    //! High-level interface for displaying a 3d object.
    template<typename tp, typename tf>
    const CImg<T>& display_object3d(const char *const title,
                                    const tp& points, const CImgList<tf>& primitives,
                                    const bool centering=true,
                                    const int render_static=4, const int render_motion=1,
                                    const bool double_sided=false, const float focale=500,
                                    const float specular_light=0.2f, const float specular_shine=0.1f,
                                    const bool display_axes=true, float *const pose_matrix=0) const {
      return display_object3d(title,points,primitives,CImgList<T>(),centering,
                              render_static,render_motion,double_sided,focale,specular_light,specular_shine,
                              display_axes,pose_matrix);
    }

    //! High-level interface for displaying a 3d object.
    template<typename tp>
    const CImg<T>& display_object3d(CImgDisplay &disp,
                                    const tp& points,
                                    const bool centering=true,
                                    const int render_static=4, const int render_motion=1,
                                    const bool double_sided=false, const float focale=500,
                                    const float specular_light=0.2f, const float specular_shine=0.1f,
                                    const bool display_axes=true, float *const pose_matrix=0) const {
      return display_object3d(disp,points,CImgList<uintT>(),centering,
                              render_static,render_motion,double_sided,focale,specular_light,specular_shine,
                              display_axes,pose_matrix);
    }

    //! High-level interface for displaying a 3d object.
    template<typename tp>
    const CImg<T>& display_object3d(const char *const title,
                                    const tp& points,
                                    const bool centering=true,
                                    const int render_static=4, const int render_motion=1,
                                    const bool double_sided=false, const float focale=500,
                                    const float specular_light=0.2f, const float specular_shine=0.1f,
                                    const bool display_axes=true, float *const pose_matrix=0) const {
      return display_object3d(title,points,CImgList<uintT>(),centering,
                              render_static,render_motion,double_sided,focale,specular_light,specular_shine,
                              display_axes,pose_matrix);
    }

    T _display_object3d_at2(const int i, const int j) const {
      return atXY(i,j,0,0,0);
    }

    template<typename tp, typename tf, typename tc, typename to>
    const CImg<T>& _display_object3d(CImgDisplay& disp, const char *const title,
                                     const tp& points, const unsigned int Npoints,
                                     const CImgList<tf>& primitives,
                                     const CImgList<tc>& colors, const to& opacities,
                                     const bool centering,
                                     const int render_static, const int render_motion,
                                     const bool double_sided, const float focale,
                                     const float specular_light, const float specular_shine,
                                     const bool display_axes, float *const pose_matrix) const {

      // Check input arguments
      if (!points || !Npoints)
        throw CImgArgumentException("CImg<%s>::display_object3d() : Given points are empty.",
                                    pixel_type());
      if (is_empty()) {
        if (disp) return CImg<T>(disp.width,disp.height,1,colors[0].size(),0).
                    _display_object3d(disp,title,points,Npoints,primitives,colors,opacities,centering,
                                     render_static,render_motion,double_sided,focale,specular_light,specular_shine,
                                     display_axes,pose_matrix);
        else return CImg<T>(cimg_fitscreen(640,480,1),1,colors[0].size(),0).
               _display_object3d(disp,title,points,Npoints,primitives,colors,opacities,centering,
                                 render_static,render_motion,double_sided,focale,specular_light,specular_shine,
                                 display_axes,pose_matrix);
      }
      if (!primitives) {
        CImgList<tf> nprimitives(Npoints,1,1,1,1);
        cimglist_for(nprimitives,l) nprimitives(l,0) = l;
        return _display_object3d(disp,title,points,Npoints,nprimitives,colors,opacities,
                                 centering,render_static,render_motion,double_sided,focale,specular_light,specular_shine,
                                 display_axes,pose_matrix);
      }
      if (!disp) {
        char ntitle[64] = { 0 }; if (!title) { cimg_std::sprintf(ntitle,"CImg<%s>",pixel_type()); }
        disp.assign(cimg_fitscreen(width,height,depth),title?title:ntitle,1);
      }

      CImgList<tc> _colors;
      if (!colors) _colors.insert(primitives.size,CImg<tc>::vector(200,200,200));
      const CImgList<tc> &ncolors = colors?colors:_colors;

      // Init 3D objects and compute object statistics
      CImg<floatT>
        pose, rot_mat, zbuffer,
        centered_points = centering?CImg<floatT>(Npoints,3):CImg<floatT>(),
        rotated_points(Npoints,3),
        bbox_points, rotated_bbox_points,
        axes_points, rotated_axes_points,
        bbox_opacities, axes_opacities;
      CImgList<uintT> bbox_primitives, axes_primitives;
      CImgList<T> bbox_colors, bbox_colors2, axes_colors;
      float dx = 0, dy = 0, dz = 0, ratio = 1;

      T minval = (T)0, maxval = (T)255;
      if (disp.normalization && colors) {
        minval = colors.minmax(maxval);
        if (minval==maxval) { minval = (T)0; maxval = (T)255; }
      }
      const float meanval = (float)mean();
      bool color_model = true;
      if (cimg::abs(meanval-minval)>cimg::abs(meanval-maxval)) color_model = false;
      const CImg<T>
        background_color(1,1,1,dim,color_model?minval:maxval),
        foreground_color(1,1,1,dim,color_model?maxval:minval);

      float xm = cimg::type<float>::max(), xM = 0, ym = xm, yM = 0, zm = xm, zM = 0;
      for (unsigned int i = 0; i<Npoints; ++i) {
        const float
          x = points._display_object3d_at2(i,0),
          y = points._display_object3d_at2(i,1),
          z = points._display_object3d_at2(i,2);
        if (x<xm) xm = x;
        if (x>xM) xM = x;
        if (y<ym) ym = y;
        if (y>yM) yM = y;
        if (z<zm) zm = z;
        if (z>zM) zM = z;
      }
      const float delta = cimg::max(xM-xm,yM-ym,zM-zm);

      if (display_axes) {
        rotated_axes_points = axes_points.assign(7,3,1,1,
                                                 0,20,0,0,22,-6,-6,
                                                 0,0,20,0,-6,22,-6,
                                                 0,0,0,20,0,0,22);
        axes_opacities.assign(3,1,1,1,1);
        axes_colors.assign(3,dim,1,1,1,foreground_color[0]);
        axes_primitives.assign(3,1,2,1,1, 0,1, 0,2, 0,3);
      }

      // Begin user interaction loop
      CImg<T> visu0(*this), visu;
      bool init = true, clicked = false, redraw = true;
      unsigned int key = 0;
      int x0 = 0, y0 = 0, x1 = 0, y1 = 0;
      disp.show().flush();

      while (!disp.is_closed && !key) {

        // Init object position and scale if necessary
        if (init) {
          ratio = delta>0?(2.0f*cimg::min(disp.width,disp.height)/(3.0f*delta)):0;
          dx = 0.5f*(xM + xm); dy = 0.5f*(yM + ym); dz = 0.5f*(zM + zm);
          if (centering) {
            cimg_forX(centered_points,l) {
              centered_points(l,0) = (float)((points(l,0) - dx)*ratio);
              centered_points(l,1) = (float)((points(l,1) - dy)*ratio);
              centered_points(l,2) = (float)((points(l,2) - dz)*ratio);
            }
          }

          if (render_static<0 || render_motion<0) {
            rotated_bbox_points = bbox_points.assign(8,3,1,1,
                                                     xm,xM,xM,xm,xm,xM,xM,xm,
                                                     ym,ym,yM,yM,ym,ym,yM,yM,
                                                     zm,zm,zm,zm,zM,zM,zM,zM);
            bbox_primitives.assign(6,1,4,1,1, 0,3,2,1, 4,5,6,7, 1,2,6,5, 0,4,7,3, 0,1,5,4, 2,3,7,6);
            bbox_colors.assign(6,dim,1,1,1,background_color[0]);
            bbox_colors2.assign(6,dim,1,1,1,foreground_color[0]);
            bbox_opacities.assign(bbox_colors.size,1,1,1,0.3f);
          }

          if (!pose) {
            if (pose_matrix) pose = CImg<floatT>(pose_matrix,4,4,1,1,false);
            else pose = CImg<floatT>::identity_matrix(4);
          }
          init = false;
          redraw = true;
        }

        // Rotate and Draw 3D object
        if (redraw) {
          const float
            r00 = pose(0,0), r10 = pose(1,0), r20 = pose(2,0), r30 = pose(3,0),
            r01 = pose(0,1), r11 = pose(1,1), r21 = pose(2,1), r31 = pose(3,1),
            r02 = pose(0,2), r12 = pose(1,2), r22 = pose(2,2), r32 = pose(3,2);
          if ((clicked && render_motion>=0) || (!clicked && render_static>=0)) {
            if (centering) cimg_forX(centered_points,l) {
                const float x = centered_points(l,0), y = centered_points(l,1), z = centered_points(l,2);
                rotated_points(l,0) = r00*x + r10*y + r20*z + r30;
                rotated_points(l,1) = r01*x + r11*y + r21*z + r31;
                rotated_points(l,2) = r02*x + r12*y + r22*z + r32;
              } else for (unsigned int l = 0; l<Npoints; ++l) {
                const float
                  x = (float)points._display_object3d_at2(l,0),
                  y = (float)points._display_object3d_at2(l,1),
                  z = (float)points._display_object3d_at2(l,2);
                rotated_points(l,0) = r00*x + r10*y + r20*z + r30;
                rotated_points(l,1) = r01*x + r11*y + r21*z + r31;
                rotated_points(l,2) = r02*x + r12*y + r22*z + r32;
              }
          } else {
            if (!centering) cimg_forX(bbox_points,l) {
                const float x = bbox_points(l,0), y = bbox_points(l,1), z = bbox_points(l,2);
                rotated_bbox_points(l,0) = r00*x + r10*y + r20*z + r30;
                rotated_bbox_points(l,1) = r01*x + r11*y + r21*z + r31;
                rotated_bbox_points(l,2) = r02*x + r12*y + r22*z + r32;
              } else cimg_forX(bbox_points,l) {
                const float x = (bbox_points(l,0)-dx)*ratio, y = (bbox_points(l,1)-dy)*ratio, z = (bbox_points(l,2)-dz)*ratio;
                rotated_bbox_points(l,0) = r00*x + r10*y + r20*z + r30;
                rotated_bbox_points(l,1) = r01*x + r11*y + r21*z + r31;
                rotated_bbox_points(l,2) = r02*x + r12*y + r22*z + r32;
              }
          }

          // Draw object
          visu = visu0;
          if ((clicked && render_motion<0) || (!clicked && render_static<0))
            visu.draw_object3d(visu.width/2.0f,visu.height/2.0f,0,rotated_bbox_points,bbox_primitives,bbox_colors,bbox_opacities,2,false,focale).
              draw_object3d(visu.width/2.0f,visu.height/2.0f,0,rotated_bbox_points,bbox_primitives,bbox_colors2,1,false,focale);
          else visu.draw_object3d(visu.width/2.0f,visu.height/2.0f,0,
                                  rotated_points,primitives,ncolors,opacities,clicked?render_motion:render_static,
                                  double_sided,focale,visu.dimx()/2.0f,visu.dimy()/2.0f,-5000,specular_light,specular_shine,
                                  (!clicked && render_static>0)?zbuffer.fill(0).ptr():0);

          // Draw axes
          if (display_axes) {
            const float Xaxes = 25, Yaxes = visu.height - 35.0f;
            cimg_forX(axes_points,l) {
              const float x = axes_points(l,0), y = axes_points(l,1), z = axes_points(l,2);
              rotated_axes_points(l,0) = r00*x + r10*y + r20*z;
              rotated_axes_points(l,1) = r01*x + r11*y + r21*z;
              rotated_axes_points(l,2) = r02*x + r12*y + r22*z;
            }
            axes_opacities(0,0) = (rotated_axes_points(1,2)>0)?0.5f:1.0f;
            axes_opacities(1,0) = (rotated_axes_points(2,2)>0)?0.5f:1.0f;
            axes_opacities(2,0) = (rotated_axes_points(3,2)>0)?0.5f:1.0f;
            visu.draw_object3d(Xaxes,Yaxes,0,rotated_axes_points,axes_primitives,axes_colors,axes_opacities,1,false,focale).
              draw_text((int)(Xaxes+rotated_axes_points(4,0)),
                        (int)(Yaxes+rotated_axes_points(4,1)),
                        "X",axes_colors[0].data,0,axes_opacities(0,0),11).
              draw_text((int)(Xaxes+rotated_axes_points(5,0)),
                        (int)(Yaxes+rotated_axes_points(5,1)),
                        "Y",axes_colors[1].data,0,axes_opacities(1,0),11).
              draw_text((int)(Xaxes+rotated_axes_points(6,0)),
                        (int)(Yaxes+rotated_axes_points(6,1)),
                        "Z",axes_colors[2].data,0,axes_opacities(2,0),11);
          }
          visu.display(disp);
          if (!clicked || render_motion==render_static) redraw = false;
        }

        // Handle user interaction
        disp.wait();
        if ((disp.button || disp.wheel) && disp.mouse_x>=0 && disp.mouse_y>=0) {
          redraw = true;
          if (!clicked) { x0 = x1 = disp.mouse_x; y0 = y1 = disp.mouse_y; if (!disp.wheel) clicked = true; }
          else { x1 = disp.mouse_x; y1 = disp.mouse_y; }
          if (disp.button&1) {
            const float
              R = 0.45f*cimg::min(disp.width,disp.height),
              R2 = R*R,
              u0 = (float)(x0-disp.dimx()/2),
              v0 = (float)(y0-disp.dimy()/2),
              u1 = (float)(x1-disp.dimx()/2),
              v1 = (float)(y1-disp.dimy()/2),
              n0 = (float)cimg_std::sqrt(u0*u0+v0*v0),
              n1 = (float)cimg_std::sqrt(u1*u1+v1*v1),
              nu0 = n0>R?(u0*R/n0):u0,
              nv0 = n0>R?(v0*R/n0):v0,
              nw0 = (float)cimg_std::sqrt(cimg::max(0,R2-nu0*nu0-nv0*nv0)),
              nu1 = n1>R?(u1*R/n1):u1,
              nv1 = n1>R?(v1*R/n1):v1,
              nw1 = (float)cimg_std::sqrt(cimg::max(0,R2-nu1*nu1-nv1*nv1)),
              u = nv0*nw1-nw0*nv1,
              v = nw0*nu1-nu0*nw1,
              w = nv0*nu1-nu0*nv1,
              n = (float)cimg_std::sqrt(u*u+v*v+w*w),
              alpha = (float)cimg_std::asin(n/R2);
            rot_mat = CImg<floatT>::rotation_matrix(u,v,w,alpha);
            rot_mat *= pose.get_crop(0,0,2,2);
            pose.draw_image(rot_mat);
            x0=x1; y0=y1;
          }
          if (disp.button&2) { pose(3,2)+=(y1-y0); x0 = x1; y0 = y1; }
          if (disp.wheel) { pose(3,2)-=focale*disp.wheel/10; disp.wheel = 0; }
          if (disp.button&4) { pose(3,0)+=(x1-x0); pose(3,1)+=(y1-y0); x0 = x1; y0 = y1; }
          if ((disp.button&1) && (disp.button&2)) { init = true; disp.button = 0; x0 = x1; y0 = y1; pose = CImg<floatT>::identity_matrix(4); }
        } else if (clicked) { x0 = x1; y0 = y1; clicked = false; redraw = true; }

        switch (key = disp.key) {
        case 0 : case cimg::keyCTRLLEFT : disp.key = key = 0; break;
        case cimg::keyD: if (disp.is_keyCTRLLEFT) {
          disp.normalscreen().resize(CImgDisplay::_fitscreen(3*disp.width/2,3*disp.height/2,1,128,-100,false),
                                     CImgDisplay::_fitscreen(3*disp.width/2,3*disp.height/2,1,128,-100,true),false).is_resized = true;
          disp.key = key = 0;
        } break;
        case cimg::keyC : if (disp.is_keyCTRLLEFT) {
          disp.normalscreen().resize(cimg_fitscreen(2*disp.width/3,2*disp.height/3,1),false).is_resized = true;
          disp.key = key = 0;
        } break;
        case cimg::keyR : if (disp.is_keyCTRLLEFT) {
          disp.normalscreen().resize(cimg_fitscreen(width,height,depth),false).is_resized = true;
          disp.key = key = 0;
        } break;
        case cimg::keyF : if (disp.is_keyCTRLLEFT) {
          disp.resize(disp.screen_dimx(),disp.screen_dimy()).toggle_fullscreen().is_resized = true;
          disp.key = key = 0;
        } break;
        case cimg::keyZ : if (disp.is_keyCTRLLEFT) { // Enable/Disable Z-buffer
          if (zbuffer) zbuffer.assign();
          else zbuffer.assign(disp.width,disp.height);
          disp.key = key = 0; redraw = true;
        } break;
        case cimg::keyS : if (disp.is_keyCTRLLEFT) { // Save snapshot
          static unsigned int snap_number = 0;
          char filename[32] = { 0 };
          cimg_std::FILE *file;
          do {
            cimg_std::sprintf(filename,"CImg_%.4u.bmp",snap_number++);
            if ((file=cimg_std::fopen(filename,"r"))!=0) cimg_std::fclose(file);
          } while (file);
          (+visu).draw_text(2,2,"Saving BMP snapshot...",foreground_color,background_color,1,11).display(disp);
          visu.save(filename);
          visu.draw_text(2,2,"Snapshot '%s' saved.",foreground_color,background_color,1,11,filename).display(disp);
          disp.key = key = 0;
        } break;
        case cimg::keyO : if (disp.is_keyCTRLLEFT) { // Save object as an .OFF file
          static unsigned int snap_number = 0;
          char filename[32] = { 0 };
          cimg_std::FILE *file;
          do {
            cimg_std::sprintf(filename,"CImg_%.4u.off",snap_number++);
            if ((file=cimg_std::fopen(filename,"r"))!=0) cimg_std::fclose(file);
          } while (file);
          visu.draw_text(2,2,"Saving object...",foreground_color,background_color,1,11).display(disp);
          points.save_off(filename,primitives,ncolors);
          visu.draw_text(2,2,"Object '%s' saved.",foreground_color,background_color,1,11,filename).display(disp);
          disp.key = key = 0;
        } break;
#ifdef cimg_use_board
        case cimg::keyP : if (disp.is_keyCTRLLEFT) { // Save object as a .EPS file
          static unsigned int snap_number = 0;
          char filename[32] = { 0 };
          cimg_std::FILE *file;
          do {
            cimg_std::sprintf(filename,"CImg_%.4u.eps",snap_number++);
            if ((file=cimg_std::fopen(filename,"r"))!=0) cimg_std::fclose(file);
          } while (file);
          visu.draw_text(2,2,"Saving EPS snapshot...",foreground_color,background_color,1,11).display(disp);
          BoardLib::Board board;
          (+visu).draw_object3d(board,visu.width/2.0f, visu.height/2.0f, 0,
                                rotated_points,primitives,ncolors,opacities,clicked?render_motion:render_static,
                                double_sided,focale,visu.dimx()/2.0f,visu.dimy()/2.0f,-5000,specular_light,specular_shine,
                                zbuffer.fill(0).ptr());
          board.saveEPS(filename);
          visu.draw_text(2,2,"Object '%s' saved.",foreground_color,background_color,1,11,filename).display(disp);
          disp.key = key = 0;
        } break;
        case cimg::keyV : if (disp.is_keyCTRLLEFT) { // Save object as a .SVG file
          static unsigned int snap_number = 0;
          char filename[32] = { 0 };
          cimg_std::FILE *file;
          do {
            cimg_std::sprintf(filename,"CImg_%.4u.svg",snap_number++);
            if ((file=cimg_std::fopen(filename,"r"))!=0) cimg_std::fclose(file);
          } while (file);
          visu.draw_text(2,2,"Saving SVG snapshot...",foreground_color,background_color,1,11).display(disp);
          BoardLib::Board board;
          (+visu).draw_object3d(board,visu.width/2.0f, visu.height/2.0f, 0,
                                rotated_points,primitives,ncolors,opacities,clicked?render_motion:render_static,
                                double_sided,focale,visu.dimx()/2.0f,visu.dimy()/2.0f,-5000,specular_light,specular_shine,
                                zbuffer.fill(0).ptr());
          board.saveSVG(filename);
          visu.draw_text(2,2,"Object '%s' saved.",foreground_color,background_color,1,11,filename).display(disp);
          disp.key = key = 0;
        } break;
#endif
        }
        if (disp.is_resized) { disp.resize(false); visu0 = get_resize(disp,1); if (zbuffer) zbuffer.assign(disp.width,disp.height); redraw = true; }
      }
      if (pose_matrix) cimg_std::memcpy(pose_matrix,pose.data,16*sizeof(float));
      disp.button = 0;
      disp.key = key;
      return *this;
    }

    //! High-level interface for displaying a graph.
    const CImg<T>& display_graph(CImgDisplay &disp,
                                 const unsigned int plot_type=1, const unsigned int vertex_type=1,
                                 const char *const labelx=0, const double xmin=0, const double xmax=0,
                                 const char *const labely=0, const double ymin=0, const double ymax=0) const {
      if (is_empty())
        throw CImgInstanceException("CImg<%s>::display_graph() : Instance image (%u,%u,%u,%u,%p) is empty.",
                                    pixel_type(),width,height,depth,dim,data);
      const unsigned int siz = width*height*depth, onormalization = disp.normalization;
      if (!disp) { char ntitle[64] = { 0 }; cimg_std::sprintf(ntitle,"CImg<%s>",pixel_type()); disp.assign(640,480,ntitle,0); }
      disp.show().flush().normalization = 0;
      double y0 = ymin, y1 = ymax, nxmin = xmin, nxmax = xmax;
      if (nxmin==nxmax) { nxmin = 0; nxmax = siz - 1.0; }
      int x0 = 0, x1 = size()/dimv()-1, key = 0;

      for (bool reset_view = true, resize_disp = false; !key && !disp.is_closed; ) {
        if (reset_view) { x0 = 0; x1 = size()/dimv()-1; y0 = ymin; y1 = ymax; reset_view = false; }
        CImg<T> zoom(x1-x0+1,1,1,dimv());
        cimg_forV(*this,k) zoom.get_shared_channel(k) = CImg<T>(ptr(x0,0,0,k),x1-x0+1,1,1,1,true);

        if (y0==y1) y0 = zoom.minmax(y1);
        if (y0==y1) { --y0; ++y1; }
        const CImg<intT> selection = zoom.get_select_graph(disp,plot_type,vertex_type,
                                                           labelx,nxmin + x0*(nxmax-nxmin)/siz,nxmin + x1*(nxmax-nxmin)/siz,
                                                           labely,y0,y1);

        const int mouse_x = disp.mouse_x, mouse_y = disp.mouse_y;
        if (selection[0]>=0 && selection[2]>=0) {
          x1 = x0 + selection[2];
          x0 += selection[0];
          if (x0==x1) reset_view = true;
          if (selection[1]>=0 && selection[3]>=0) {
            y0 = y1 - selection[3]*(y1-y0)/(disp.dimy()-32);
            y1 -= selection[1]*(y1-y0)/(disp.dimy()-32);
          }
        } else {
          bool go_in = false, go_out = false, go_left = false, go_right = false, go_up = false, go_down = false;
          switch (key = disp.key) {
          case cimg::keyHOME : case cimg::keyBACKSPACE : reset_view = resize_disp = true; key = 0; break;
          case cimg::keyPADADD : go_in = true; key = 0; break;
          case cimg::keyPADSUB : go_out = true; key = 0; break;
          case cimg::keyARROWLEFT : case cimg::keyPAD4 : go_left = true; key = 0; break;
          case cimg::keyARROWRIGHT : case cimg::keyPAD6 : go_right = true; key = 0; break;
          case cimg::keyARROWUP : case cimg::keyPAD8 : go_up = true; key = 0; break;
          case cimg::keyARROWDOWN : case cimg::keyPAD2 : go_down = true; key = 0; break;
          case cimg::keyPAD7 : go_left = true; go_up = true; key = 0; break;
          case cimg::keyPAD9 : go_right = true; go_up = true; key = 0; break;
          case cimg::keyPAD1 : go_left = true; go_down = true; key = 0; break;
          case cimg::keyPAD3 : go_right = true; go_down = true; key = 0; break;
          }
          if (disp.wheel) go_out = !(go_in = disp.wheel>0);

          if (go_in) {
            const int
              xsiz = x1 - x0,
              mx = (mouse_x-16)*xsiz/(disp.dimx()-32),
              cx = x0 + (mx<0?0:(mx>=xsiz?xsiz:mx));
            if (x1-x0>4) {
              x0 = cx - 7*(cx-x0)/8; x1 = cx + 7*(x1-cx)/8;
              if (disp.is_keyCTRLLEFT) {
                const double
                  ysiz = y1 - y0,
                  my = (mouse_y-16)*ysiz/(disp.dimy()-32),
                  cy = y1 - (my<0?0:(my>=ysiz?ysiz:my));
                y0 = cy - 7*(cy-y0)/8; y1 = cy + 7*(y1-cy)/8;
              } else y0 = y1 = 0;
            }
          }
          if (go_out) {
            const int deltax = (x1-x0)/8, ndeltax = deltax?deltax:(siz>1?1:0);
            x0-=ndeltax; x1+=ndeltax;
            if (x0<0) { x1-=x0; x0 = 0; if (x1>=(int)siz) x1 = (int)siz-1; }
            if (x1>=(int)siz) { x0-=(x1-siz+1); x1 = (int)siz-1; if (x0<0) x0 = 0; }
            if (disp.is_keyCTRLLEFT) {
              const double deltay = (y1-y0)/8, ndeltay = deltay?deltay:0.01;
              y0-=ndeltay; y1+=ndeltay;
            }
          }
          if (go_left) {
            const int delta = (x1-x0)/5, ndelta = delta?delta:1;
            if (x0-ndelta>=0) { x0-=ndelta; x1-=ndelta; }
            else { x1-=x0; x0 = 0; }
            go_left = false;
          }
          if (go_right) {
            const int delta = (x1-x0)/5, ndelta = delta?delta:1;
            if (x1+ndelta<(int)siz) { x0+=ndelta; x1+=ndelta; }
            else { x0+=(siz-1-x1); x1 = siz-1; }
            go_right = false;
          }
          if (go_up) {
            const double delta = (y1-y0)/10, ndelta = delta?delta:1;
            y0+=ndelta; y1+=ndelta;
            go_up = false;
          }
          if (go_down) {
            const double delta = (y1-y0)/10, ndelta = delta?delta:1;
            y0-=ndelta; y1-=ndelta;
            go_down = false;
          }
        }
      }
      disp.normalization = onormalization;
      return *this;
    }

    //! High-level interface for displaying a graph.
    const CImg<T>& display_graph(const char *const title=0,
                                 const unsigned int plot_type=1, const unsigned int vertex_type=1,
                                 const char *const labelx=0, const double xmin=0, const double xmax=0,
                                 const char *const labely=0, const double ymin=0, const double ymax=0) const {
      if (is_empty())
        throw CImgInstanceException("CImg<%s>::display_graph() : Instance image (%u,%u,%u,%u,%p) is empty.",
                                    pixel_type(),width,height,depth,dim,data);
      char ntitle[64] = { 0 }; if (!title) cimg_std::sprintf(ntitle,"CImg<%s>",pixel_type());
      CImgDisplay disp(cimg_fitscreen(640,480,1),title?title:ntitle,0);
      return display_graph(disp,plot_type,vertex_type,labelx,xmin,xmax,labely,ymin,ymax);
    }

    //! Select sub-graph in a graph.
    CImg<intT> get_select_graph(CImgDisplay &disp,
                                const unsigned int plot_type=1, const unsigned int vertex_type=1,
                                const char *const labelx=0, const double xmin=0, const double xmax=0,
                                const char *const labely=0, const double ymin=0, const double ymax=0) const {
      if (is_empty())
        throw CImgInstanceException("CImg<%s>::display_graph() : Instance image (%u,%u,%u,%u,%p) is empty.",
                                    pixel_type(),width,height,depth,dim,data);
      const unsigned int siz = width*height*depth, onormalization = disp.normalization;
      if (!disp) { char ntitle[64] = { 0 }; cimg_std::sprintf(ntitle,"CImg<%s>",pixel_type()); disp.assign(640,480,ntitle,0); }
      disp.show().key = disp.normalization = disp.button = disp.wheel = 0;  // Must keep 'key' field unchanged.
      double nymin = ymin, nymax = ymax, nxmin = xmin, nxmax = xmax;
      if (nymin==nymax) nymin = (Tfloat)minmax(nymax);
      if (nymin==nymax) { --nymin; ++nymax; }
      if (nxmin==nxmax && nxmin==0) { nxmin = 0; nxmax = siz - 1.0; }

      const unsigned char black[] = { 0,0,0 }, white[] = { 255,255,255 }, gray[] = { 220,220,220 };
      const unsigned char gray2[] = { 110,110,110 }, ngray[] = { 35,35,35 };
      static unsigned int odimv = 0;
      static CImg<ucharT> palette;
      if (odimv!=dim) {
        odimv = dim;
        palette = CImg<ucharT>(3,dim,1,1,120).noise(70,1);
        if (dim==1) { palette[0] = palette[1] = 120; palette[2] = 200; }
        else {
          palette(0,0) = 220; palette(1,0) = 10; palette(2,0) = 10;
          if (dim>1) { palette(0,1) = 10; palette(1,1) = 220; palette(2,1) = 10; }
          if (dim>2) { palette(0,2) = 10; palette(1,2) = 10; palette(2,2) = 220; }
        }
      }

      CImg<ucharT> visu0, visu, graph, text, axes;
      const unsigned int whz = width*height*depth;
      int x0 = -1, x1 = -1, y0 = -1, y1 = -1, omouse_x = -2, omouse_y = -2;
      char message[1024] = { 0 };
      unsigned int okey = 0, obutton = 0;
      CImg_3x3(I,unsigned char);

      for (bool selected = false; !selected && !disp.is_closed && !okey && !disp.wheel; ) {
        const int mouse_x = disp.mouse_x, mouse_y = disp.mouse_y;
        const unsigned int key = disp.key, button = disp.button;

        // Generate graph representation.
        if (!visu0) {
          visu0.assign(disp.dimx(),disp.dimy(),1,3,220);
          const int gdimx = disp.dimx() - 32, gdimy = disp.dimy() - 32;
          if (gdimx>0 && gdimy>0) {
            graph.assign(gdimx,gdimy,1,3,255);
            graph.draw_grid(-10,-10,0,0,false,true,black,0.2f,0x33333333,0x33333333);
            cimg_forV(*this,k) graph.draw_graph(get_shared_channel(k),&palette(0,k),(plot_type!=3 || dim==1)?1:0.6f,
                                                plot_type,vertex_type,nymax,nymin);

            axes.assign(gdimx,gdimy,1,1,0);
            const float
              dx = (float)cimg::abs(nxmax-nxmin), dy = (float)cimg::abs(nymax-nymin),
              px = (float)cimg_std::pow(10.0,(int)cimg_std::log10(dx)-2.0),
              py = (float)cimg_std::pow(10.0,(int)cimg_std::log10(dy)-2.0);
            const CImg<Tdouble>
              seqx = CImg<Tdouble>::sequence(1 + gdimx/60,nxmin,nxmax).round(px),
              seqy = CImg<Tdouble>::sequence(1 + gdimy/60,nymax,nymin).round(py);
            axes.draw_axis(seqx,seqy,white);
            if (nymin>0) axes.draw_axis(seqx,gdimy-1,gray);
            if (nymax<0) axes.draw_axis(seqx,0,gray);
            if (nxmin>0) axes.draw_axis(0,seqy,gray);
            if (nxmax<0) axes.draw_axis(gdimx-1,seqy,gray);

            cimg_for3x3(axes,x,y,0,0,I)
              if (Icc) {
                if (Icc==255) cimg_forV(graph,k) graph(x,y,k) = 0;
                else cimg_forV(graph,k) graph(x,y,k) = (unsigned char)(2*graph(x,y,k)/3);
              }
              else if (Ipc || Inc || Icp || Icn || Ipp || Inn || Ipn || Inp) cimg_forV(graph,k) graph(x,y,k) = (graph(x,y,k)+255)/2;

            visu0.draw_image(16,16,graph);
            visu0.draw_line(15,15,16+gdimx,15,gray2).draw_line(16+gdimx,15,16+gdimx,16+gdimy,gray2).
              draw_line(16+gdimx,16+gdimy,15,16+gdimy,white).draw_line(15,16+gdimy,15,15,white);
          } else graph.assign();
          text.assign().draw_text(0,0,labelx?labelx:"X-axis",white,ngray,1);
          visu0.draw_image((visu0.dimx()-text.dimx())/2,visu0.dimy()-14,~text);
          text.assign().draw_text(0,0,labely?labely:"Y-axis",white,ngray,1).rotate(-90);
          visu0.draw_image(2,(visu0.dimy()-text.dimy())/2,~text);
          visu.assign();
        }

        // Generate and display current view.
        if (!visu) {
          visu.assign(visu0);
          if (graph && x0>=0 && x1>=0) {
            const int
              nx0 = x0<=x1?x0:x1,
              nx1 = x0<=x1?x1:x0,
              ny0 = y0<=y1?y0:y1,
              ny1 = y0<=y1?y1:y0,
              sx0 = 16 + nx0*(visu.dimx()-32)/whz,
              sx1 = 15 + (nx1+1)*(visu.dimx()-32)/whz,
              sy0 = 16 + ny0,
              sy1 = 16 + ny1;

            if (y0>=0 && y1>=0)
              visu.draw_rectangle(sx0,sy0,sx1,sy1,gray,0.5f).draw_rectangle(sx0,sy0,sx1,sy1,black,0.5f,0xCCCCCCCCU);
            else visu.draw_rectangle(sx0,0,sx1,visu.dimy()-17,gray,0.5f).
                   draw_line(sx0,16,sx0,visu.dimy()-17,black,0.5f,0xCCCCCCCCU).
                   draw_line(sx1,16,sx1,visu.dimy()-17,black,0.5f,0xCCCCCCCCU);
          }
          if (mouse_x>=16 && mouse_y>=16 && mouse_x<visu.dimx()-16 && mouse_y<visu.dimy()-16) {
            if (graph) visu.draw_line(mouse_x,16,mouse_x,visu.dimy()-17,black,0.5f,0x55555555U);
            const unsigned x = (mouse_x-16)*whz/(disp.dimx()-32);
            const double cx = nxmin + x*(nxmax-nxmin)/whz;
            if (dim>=7)
              cimg_std::sprintf(message,"Value[%g] = ( %g %g %g ... %g %g %g )",cx,
                           (double)(*this)(x,0,0,0),(double)(*this)(x,0,0,1),(double)(*this)(x,0,0,2),
                           (double)(*this)(x,0,0,dim-4),(double)(*this)(x,0,0,dim-3),(double)(*this)(x,0,0,dim-1));
            else {
              cimg_std::sprintf(message,"Value[%g] = ( ",cx);
              cimg_forV(*this,k) cimg_std::sprintf(message+cimg::strlen(message),"%g ",(double)(*this)(x,0,0,k));
              cimg_std::sprintf(message+cimg::strlen(message),")");
            }
            if (x0>=0 && x1>=0) {
              const int
                 nx0 = x0<=x1?x0:x1,
                 nx1 = x0<=x1?x1:x0,
                 ny0 = y0<=y1?y0:y1,
                 ny1 = y0<=y1?y1:y0;
              const double
                 cx0 = nxmin + nx0*(nxmax-nxmin)/(visu.dimx()-32),
                 cx1 = nxmin + nx1*(nxmax-nxmin)/(visu.dimx()-32),
                 cy0 = nymax - ny0*(nymax-nymin)/(visu.dimy()-32),
                 cy1 = nymax - ny1*(nymax-nymin)/(visu.dimy()-32);
              if (y0>=0 && y1>=0)
                cimg_std::sprintf(message+cimg::strlen(message)," - Range ( %g, %g ) - ( %g, %g )",cx0,cy0,cx1,cy1);
              else
                cimg_std::sprintf(message+cimg::strlen(message)," - Range [ %g - %g ]",cx0,cx1);
            }
            text.assign().draw_text(0,0,message,white,ngray,1);
            visu.draw_image((visu.dimx()-text.dimx())/2,2,~text);
          }
          visu.display(disp);
        }

        // Test keys.
        switch (okey = key) {
        case cimg::keyCTRLLEFT : okey = 0; break;
        case cimg::keyD : if (disp.is_keyCTRLLEFT) {
          disp.normalscreen().resize(CImgDisplay::_fitscreen(3*disp.width/2,3*disp.height/2,1,128,-100,false),
                                     CImgDisplay::_fitscreen(3*disp.width/2,3*disp.height/2,1,128,-100,true),false).is_resized = true;
          disp.key = okey = 0;
        } break;
        case cimg::keyC : if (disp.is_keyCTRLLEFT) {
          disp.normalscreen().resize(cimg_fitscreen(2*disp.width/3,2*disp.height/3,1),false).is_resized = true;
          disp.key = okey = 0;
        } break;
        case cimg::keyR : if (disp.is_keyCTRLLEFT) {
          disp.normalscreen().resize(cimg_fitscreen(640,480,1),false).is_resized = true;
          disp.key = okey = 0;
        } break;
        case cimg::keyF : if (disp.is_keyCTRLLEFT) {
          disp.resize(disp.screen_dimx(),disp.screen_dimy()).toggle_fullscreen().is_resized = true;
          disp.key = okey = 0;
        } break;
        case cimg::keyS : if (disp.is_keyCTRLLEFT) {
          static unsigned int snap_number = 0;
          if (visu || visu0) {
            CImg<ucharT> &screen = visu?visu:visu0;
            char filename[32] = { 0 };
            cimg_std::FILE *file;
            do {
              cimg_std::sprintf(filename,"CImg_%.4u.bmp",snap_number++);
              if ((file=cimg_std::fopen(filename,"r"))!=0) cimg_std::fclose(file);
            } while (file);
            (+screen).draw_text(2,2,"Saving BMP snapshot...",black,gray,1,11).display(disp);
            screen.save(filename);
            screen.draw_text(2,2,"Snapshot '%s' saved.",black,gray,1,11,filename).display(disp);
          }
          disp.key = okey = 0;
        } break;
        }

        // Handle mouse motion and mouse buttons
        if (obutton!=button || omouse_x!=mouse_x || omouse_y!=mouse_y) {
          visu.assign();
          if (disp.mouse_x>=0 && disp.mouse_y>=0) {
            const int
              mx = (mouse_x-16)*(int)whz/(disp.dimx()-32),
              cx = mx<0?0:(mx>=(int)whz?whz-1:mx),
              my = mouse_y-16,
              cy = my<=0?0:(my>=(disp.dimy()-32)?(disp.dimy()-32):my);
            if (button&1) { if (!obutton) { x0 = cx; y0 = -1; } else { x1 = cx; y1 = -1; }}
            else if (button&2) { if (!obutton) { x0 = cx; y0 = cy; } else { x1 = cx; y1 = cy; }}
            else if (obutton) { x1 = cx; y1 = y1>=0?cy:-1; selected = true; }
          } else if (!button && obutton) selected = true;
          obutton = button; omouse_x = mouse_x; omouse_y = mouse_y;
        }
        if (disp.is_resized) { disp.resize(false); visu0.assign(); }
        if (visu && visu0) disp.wait();
      }
      disp.normalization = onormalization;
      if (x1<x0) cimg::swap(x0,x1);
      if (y1<y0) cimg::swap(y0,y1);
      disp.key = okey;
      return CImg<intT>(4,1,1,1,x0,y0,x1,y1);
    }

    //@}
    //---------------------------
    //
    //! \name Image File Loading
    //@{
    //---------------------------

    //! Load an image from a file.
    /**
       \param filename is the name of the image file to load.
       \note The extension of \c filename defines the file format. If no filename
       extension is provided, CImg<T>::get_load() will try to load a .cimg file.
    **/
    CImg<T>& load(const char *const filename) {
      if (!filename)
        throw CImgArgumentException("CImg<%s>::load() : Cannot load (null) filename.",
                                    pixel_type());
      const char *ext = cimg::split_filename(filename);
      const unsigned int odebug = cimg::exception_mode();
      cimg::exception_mode() = 0;
      assign();
      try {
#ifdef cimg_load_plugin
        cimg_load_plugin(filename);
#endif
#ifdef cimg_load_plugin1
        cimg_load_plugin1(filename);
#endif
#ifdef cimg_load_plugin2
        cimg_load_plugin2(filename);
#endif
#ifdef cimg_load_plugin3
        cimg_load_plugin3(filename);
#endif
#ifdef cimg_load_plugin4
        cimg_load_plugin4(filename);
#endif
#ifdef cimg_load_plugin5
        cimg_load_plugin5(filename);
#endif
#ifdef cimg_load_plugin6
        cimg_load_plugin6(filename);
#endif
#ifdef cimg_load_plugin7
        cimg_load_plugin7(filename);
#endif
#ifdef cimg_load_plugin8
        cimg_load_plugin8(filename);
#endif
        // ASCII formats
        if (!cimg::strcasecmp(ext,"asc")) load_ascii(filename);
        if (!cimg::strcasecmp(ext,"dlm") ||
            !cimg::strcasecmp(ext,"txt")) load_dlm(filename);

        // 2D binary formats
        if (!cimg::strcasecmp(ext,"bmp")) load_bmp(filename);
        if (!cimg::strcasecmp(ext,"jpg") ||
            !cimg::strcasecmp(ext,"jpeg") ||
            !cimg::strcasecmp(ext,"jpe") ||
            !cimg::strcasecmp(ext,"jfif") ||
            !cimg::strcasecmp(ext,"jif")) load_jpeg(filename);
        if (!cimg::strcasecmp(ext,"png")) load_png(filename);
        if (!cimg::strcasecmp(ext,"ppm") ||
            !cimg::strcasecmp(ext,"pgm") ||
            !cimg::strcasecmp(ext,"pnm")) load_pnm(filename);
        if (!cimg::strcasecmp(ext,"tif") ||
            !cimg::strcasecmp(ext,"tiff")) load_tiff(filename);
        if (!cimg::strcasecmp(ext,"cr2") ||
            !cimg::strcasecmp(ext,"crw") ||
            !cimg::strcasecmp(ext,"dcr") ||
            !cimg::strcasecmp(ext,"mrw") ||
            !cimg::strcasecmp(ext,"nef") ||
            !cimg::strcasecmp(ext,"orf") ||
            !cimg::strcasecmp(ext,"pix") ||
            !cimg::strcasecmp(ext,"ptx") ||
            !cimg::strcasecmp(ext,"raf") ||
            !cimg::strcasecmp(ext,"srf")) load_dcraw_external(filename);

        // 3D binary formats
        if (!cimg::strcasecmp(ext,"dcm") ||
            !cimg::strcasecmp(ext,"dicom")) load_medcon_external(filename);
        if (!cimg::strcasecmp(ext,"hdr") ||
            !cimg::strcasecmp(ext,"nii")) load_analyze(filename);
        if (!cimg::strcasecmp(ext,"par") ||
            !cimg::strcasecmp(ext,"rec")) load_parrec(filename);
        if (!cimg::strcasecmp(ext,"inr")) load_inr(filename);
        if (!cimg::strcasecmp(ext,"pan")) load_pandore(filename);
        if (!cimg::strcasecmp(ext,"cimg") ||
            !cimg::strcasecmp(ext,"cimgz") ||
            *ext=='\0')  return load_cimg(filename);

        // Archive files
        if (!cimg::strcasecmp(ext,"gz")) load_gzip_external(filename);

        // Image sequences
        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,"mkv") ||
            !cimg::strcasecmp(ext,"mpe") ||
            !cimg::strcasecmp(ext,"movie") ||
            !cimg::strcasecmp(ext,"ogm") ||
            !cimg::strcasecmp(ext,"qt") ||
            !cimg::strcasecmp(ext,"rm") ||
            !cimg::strcasecmp(ext,"vob") ||
            !cimg::strcasecmp(ext,"wmv") ||
            !cimg::strcasecmp(ext,"xvid") ||
            !cimg::strcasecmp(ext,"mpeg")) load_ffmpeg(filename);
        if (is_empty()) throw CImgIOException("CImg<%s>::load()",pixel_type());
      } catch (CImgException& e) {
        if (!cimg::strncasecmp(e.message,"cimg::fopen()",13)) {
          cimg::exception_mode() = odebug;
          throw CImgIOException("CImg<%s>::load() : File '%s' cannot be opened.",pixel_type(),filename);
        } else try {
          const char *const ftype = cimg::file_type(0,filename);
          assign();
          if (!cimg::strcmp(ftype,"pnm")) load_pnm(filename);
          if (!cimg::strcmp(ftype,"bmp")) load_bmp(filename);
          if (!cimg::strcmp(ftype,"jpeg")) load_jpeg(filename);
          if (!cimg::strcmp(ftype,"pan")) load_pandore(filename);
          if (!cimg::strcmp(ftype,"png")) load_png(filename);
          if (!cimg::strcmp(ftype,"tiff")) load_tiff(filename);
          if (is_empty()) throw CImgIOException("CImg<%s>::load()",pixel_type());
        } catch (CImgException&) {
          try {
            load_other(filename);
          } catch (CImgException&) {
            assign();
          }
        }
      }
      cimg::exception_mode() = odebug;
      if (is_empty())
        throw CImgIOException("CImg<%s>::load() : File '%s', format not recognized.",pixel_type(),filename);
      return *this;
    }

    static CImg<T> get_load(const char *const filename) {
      return CImg<T>().load(filename);
    }

    //! Load an image from an ASCII file.
    CImg<T>& load_ascii(const char *const filename) {
      return _load_ascii(0,filename);
    }

    static CImg<T> get_load_ascii(const char *const filename) {
      return CImg<T>().load_ascii(filename);
    }

    //! Load an image from an ASCII file.
    CImg<T>& load_ascii(cimg_std::FILE *const file) {
      return _load_ascii(file,0);
    }

    static CImg<T> get_load_ascii(cimg_std::FILE *const file) {
      return CImg<T>().load_ascii(file);
    }

    CImg<T>& _load_ascii(cimg_std::FILE *const file, const char *const filename) {
      if (!filename && !file)
        throw CImgArgumentException("CImg<%s>::load_ascii() : Cannot load (null) filename.",
                                    pixel_type());
      cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"rb");
      char line[256] = { 0 };
      int err = cimg_std::fscanf(nfile,"%*[^0-9]%255[^\n]",line);
      unsigned int off, dx = 0, dy = 1, dz = 1, dv = 1;
      cimg_std::sscanf(line,"%u%*c%u%*c%u%*c%u",&dx,&dy,&dz,&dv);
      err = cimg_std::fscanf(nfile,"%*[^0-9.+-]");
      if (!dx || !dy || !dz || !dv) {
        if (!file) cimg::fclose(nfile);
        throw CImgIOException("CImg<%s>::load_ascii() : File '%s', invalid .ASC header, specified image dimensions are (%u,%u,%u,%u).",
                              pixel_type(),filename?filename:"(FILE*)",dx,dy,dz,dv);
      }
      assign(dx,dy,dz,dv);
      const unsigned long siz = size();
      double val;
      T *ptr = data;
      for (err = 1, off = 0; off<siz && err==1; ++off) {
        err = cimg_std::fscanf(nfile,"%lf%*[^0-9.+-]",&val);
        *(ptr++) = (T)val;
      }
      if (err!=1)
        cimg::warn("CImg<%s>::load_ascii() : File '%s', only %u/%lu values read.",
                   pixel_type(),filename?filename:"(FILE*)",off-1,siz);
      if (!file) cimg::fclose(nfile);
      return *this;
    }

    //! Load an image from a DLM file.
    CImg<T>& load_dlm(const char *const filename) {
      return _load_dlm(0,filename);
    }

    static CImg<T> get_load_dlm(const char *const filename) {
      return CImg<T>().load_dlm(filename);
    }

    //! Load an image from a DLM file.
    CImg<T>& load_dlm(cimg_std::FILE *const file) {
      return _load_dlm(file,0);
    }

    static CImg<T> get_load_dlm(cimg_std::FILE *const file) {
      return CImg<T>().load_dlm(file);
    }

    CImg<T>& _load_dlm(cimg_std::FILE *const file, const char *const filename) {
      if (!filename && !file)
        throw CImgArgumentException("CImg<%s>::load_dlm() : Cannot load (null) filename.",
                                    pixel_type());
      cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"r");
      assign(256,256);
      char c, delimiter[256] = { 0 }, tmp[256];
      unsigned int cdx = 0, dx = 0, dy = 0;
      int oerr = 0, err;
      double val;
      while ((err = cimg_std::fscanf(nfile,"%lf%255[^0-9.+-]",&val,delimiter))!=EOF) {
        oerr = err;
        if (err>0) (*this)(cdx++,dy) = (T)val;
        if (cdx>=width) resize(width+256,1,1,1,0);
        c = 0; if (!cimg_std::sscanf(delimiter,"%255[^\n]%c",tmp,&c) || c=='\n') {
          dx = cimg::max(cdx,dx);
          ++dy;
          if (dy>=height) resize(width,height+256,1,1,0);
          cdx = 0;
        }
      }
      if (cdx && oerr==1) { dx=cdx; ++dy; }
      if (!dx || !dy) {
        if (!file) cimg::fclose(nfile);
        throw CImgIOException("CImg<%s>::load_dlm() : File '%s', invalid DLM file, specified image dimensions are (%u,%u).",
                              pixel_type(),filename?filename:"(FILE*)",dx,dy);
      }
      resize(dx,dy,1,1,0);
      if (!file) cimg::fclose(nfile);
      return *this;
    }

    //! Load an image from a BMP file.
    CImg<T>& load_bmp(const char *const filename) {
      return _load_bmp(0,filename);
    }

    static CImg<T> get_load_bmp(const char *const filename) {
      return CImg<T>().load_bmp(filename);
    }

    //! Load an image from a BMP file.
    CImg<T>& load_bmp(cimg_std::FILE *const file) {
      return _load_bmp(file,0);
    }

    static CImg<T> get_load_bmp(cimg_std::FILE *const file) {
      return CImg<T>().load_bmp(file);
    }

    CImg<T>& _load_bmp(cimg_std::FILE *const file, const char *const filename) {
      if (!filename && !file)
        throw CImgArgumentException("CImg<%s>::load_bmp() : Cannot load (null) filename.",
                                    pixel_type());
      cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"rb");
      unsigned char header[64];
      cimg::fread(header,54,nfile);
      if (header[0]!='B' || header[1]!='M') {
        if (!file) cimg::fclose(nfile);
        throw CImgIOException("CImg<%s>::load_bmp() : Invalid valid BMP file (filename '%s').",
                              pixel_type(),filename?filename:"(FILE*)");
      }
      assign();

      // Read header and pixel buffer
      int
        file_size = header[0x02] + (header[0x03]<<8) + (header[0x04]<<16) + (header[0x05]<<24),
        offset = header[0x0A] + (header[0x0B]<<8) + (header[0x0C]<<16) + (header[0x0D]<<24),
        dx = header[0x12] + (header[0x13]<<8) + (header[0x14]<<16) + (header[0x15]<<24),
        dy = header[0x16] + (header[0x17]<<8) + (header[0x18]<<16) + (header[0x19]<<24),
        compression = header[0x1E] + (header[0x1F]<<8) + (header[0x20]<<16) + (header[0x21]<<24),
        nb_colors = header[0x2E] + (header[0x2F]<<8) + (header[0x30]<<16) + (header[0x31]<<24),
        bpp = header[0x1C] + (header[0x1D]<<8),
        *palette = 0;
      const int
        dx_bytes = (bpp==1)?(dx/8+(dx%8?1:0)):((bpp==4)?(dx/2+(dx%2?1:0)):(dx*bpp/8)),
        align = (4-dx_bytes%4)%4,
        buf_size = cimg::min(cimg::abs(dy)*(dx_bytes+align),file_size-offset);

      if (bpp<16) { if (!nb_colors) nb_colors=1<<bpp; } else nb_colors = 0;
      if (nb_colors) { palette = new int[nb_colors]; cimg::fread(palette,nb_colors,nfile); }
      const int xoffset = offset-54-4*nb_colors;
      if (xoffset>0) cimg_std::fseek(nfile,xoffset,SEEK_CUR);
      unsigned char *buffer = new unsigned char[buf_size], *ptrs = buffer;
      cimg::fread(buffer,buf_size,nfile);
      if (!file) cimg::fclose(nfile);

      // Decompress buffer (if necessary)
      if (compression) {
        delete[] buffer;
        if (file) {
          throw CImgIOException("CImg<%s>::load_bmp() : Not able to read a compressed BMP file using a *FILE input",
                                pixel_type());
        } else return load_other(filename);
      }

      // Read pixel data
      assign(dx,cimg::abs(dy),1,3);
      switch (bpp) {
      case 1 : { // Monochrome
        for (int y=height-1; y>=0; --y) {
          unsigned char mask = 0x80, val = 0;
          cimg_forX(*this,x) {
            if (mask==0x80) val = *(ptrs++);
            const unsigned char *col = (unsigned char*)(palette+(val&mask?1:0));
            (*this)(x,y,2) = (T)*(col++);
            (*this)(x,y,1) = (T)*(col++);
            (*this)(x,y,0) = (T)*(col++);
            mask = cimg::ror(mask);
          } ptrs+=align; }
      } break;
      case 4 : { // 16 colors
        for (int y=height-1; y>=0; --y) {
          unsigned char mask = 0xF0, val = 0;
          cimg_forX(*this,x) {
            if (mask==0xF0) val = *(ptrs++);
            const unsigned char color = (unsigned char)((mask<16)?(val&mask):((val&mask)>>4));
            unsigned char *col = (unsigned char*)(palette+color);
            (*this)(x,y,2) = (T)*(col++);
            (*this)(x,y,1) = (T)*(col++);
            (*this)(x,y,0) = (T)*(col++);
            mask = cimg::ror(mask,4);
          } ptrs+=align; }
      } break;
      case 8 : { //  256 colors
        for (int y=height-1; y>=0; --y) { cimg_forX(*this,x) {
          const unsigned char *col = (unsigned char*)(palette+*(ptrs++));
          (*this)(x,y,2) = (T)*(col++);
          (*this)(x,y,1) = (T)*(col++);
          (*this)(x,y,0) = (T)*(col++);
        } ptrs+=align; }
      } break;
      case 16 : { // 16 bits colors
        for (int y=height-1; y>=0; --y) { cimg_forX(*this,x) {
          const unsigned char c1 = *(ptrs++), c2 = *(ptrs++);
          const unsigned short col = (unsigned short)(c1|(c2<<8));
          (*this)(x,y,2) = (T)(col&0x1F);
          (*this)(x,y,1) = (T)((col>>5)&0x1F);
          (*this)(x,y,0) = (T)((col>>10)&0x1F);
        } ptrs+=align; }
      } break;
      case 24 : { // 24 bits colors
        for (int y=height-1; y>=0; --y) { cimg_forX(*this,x) {
          (*this)(x,y,2) = (T)*(ptrs++);
          (*this)(x,y,1) = (T)*(ptrs++);
          (*this)(x,y,0) = (T)*(ptrs++);
        } ptrs+=align; }
      } break;
      case 32 : { // 32 bits colors
        for (int y=height-1; y>=0; --y) { cimg_forX(*this,x) {
          (*this)(x,y,2) = (T)*(ptrs++);
          (*this)(x,y,1) = (T)*(ptrs++);
          (*this)(x,y,0) = (T)*(ptrs++);
          ++ptrs;
        } ptrs+=align; }
      } break;
      }
      if (palette) delete[] palette;
      delete[] buffer;
      if (dy<0) mirror('y');
      return *this;
    }

    //! Load an image from a JPEG file.
    CImg<T>& load_jpeg(const char *const filename) {
      return _load_jpeg(0,filename);
    }

    static CImg<T> get_load_jpeg(const char *const filename) {
      return CImg<T>().load_jpeg(filename);
    }

    //! Load an image from a JPEG file.
    CImg<T>& load_jpeg(cimg_std::FILE *const file) {
      return _load_jpeg(file,0);
    }

    static CImg<T> get_load_jpeg(cimg_std::FILE *const file) {
      return CImg<T>().load_jpeg(file);
    }

    CImg<T>& _load_jpeg(cimg_std::FILE *const file, const char *const filename) {
      if (!filename && !file)
        throw CImgArgumentException("CImg<%s>::load_jpeg() : Cannot load (null) filename.",
                                    pixel_type());
#ifndef cimg_use_jpeg
      if (file)
        throw CImgIOException("CImg<%s>::load_jpeg() : File '(FILE*)' cannot be read without using libjpeg.",
                              pixel_type());
      else return load_other(filename);
#else
      struct jpeg_decompress_struct cinfo;
      struct jpeg_error_mgr jerr;
      cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"rb");

      cinfo.err = jpeg_std_error(&jerr);
      jpeg_create_decompress(&cinfo);
      jpeg_stdio_src(&cinfo,nfile);
      jpeg_read_header(&cinfo,TRUE);
      jpeg_start_decompress(&cinfo);

      if (cinfo.output_components!=1 && cinfo.output_components!=3 && cinfo.output_components!=4) {
        cimg::warn("CImg<%s>::load_jpeg() : Don't know how to read image '%s' with libpeg, trying ImageMagick's convert",
                   pixel_type(),filename?filename:"(FILE*)");
        if (!file) return load_other(filename);
        else {
          if (!file) cimg::fclose(nfile);
          throw CImgIOException("CImg<%s>::load_jpeg() : Cannot read JPEG image '%s' using a *FILE input.",
                                pixel_type(),filename?filename:"(FILE*)");
        }
      }

      const unsigned int row_stride = cinfo.output_width * cinfo.output_components;
      unsigned char *buf = new unsigned char[cinfo.output_width*cinfo.output_height*cinfo.output_components], *buf2 = buf;
      JSAMPROW row_pointer[1];
      while (cinfo.output_scanline < cinfo.output_height) {
        row_pointer[0] = &buf[cinfo.output_scanline*row_stride];
        jpeg_read_scanlines(&cinfo,row_pointer,1);
      }
      jpeg_finish_decompress(&cinfo);
      jpeg_destroy_decompress(&cinfo);
      if (!file) cimg::fclose(nfile);

      assign(cinfo.output_width,cinfo.output_height,1,cinfo.output_components);
      switch (dim) {
      case 1 : {
        T *ptr_g = data;
        cimg_forXY(*this,x,y) *(ptr_g++) = (T)*(buf2++);
      } break;
      case 3 : {
        T *ptr_r = ptr(0,0,0,0), *ptr_g = ptr(0,0,0,1), *ptr_b = ptr(0,0,0,2);
        cimg_forXY(*this,x,y) {
          *(ptr_r++) = (T)*(buf2++);
          *(ptr_g++) = (T)*(buf2++);
          *(ptr_b++) = (T)*(buf2++);
        }
      } break;
      case 4 : {
        T *ptr_r = ptr(0,0,0,0), *ptr_g = ptr(0,0,0,1),
          *ptr_b = ptr(0,0,0,2), *ptr_a = ptr(0,0,0,3);
        cimg_forXY(*this,x,y) {
          *(ptr_r++) = (T)*(buf2++);
          *(ptr_g++) = (T)*(buf2++);
          *(ptr_b++) = (T)*(buf2++);
          *(ptr_a++) = (T)*(buf2++);
        }
      } break;
      }
      delete[] buf;
      return *this;
#endif
    }

    //! Load an image from a file, using Magick++ library.
    // Added April/may 2006 by Christoph Hormann <chris_hormann@gmx.de>
    //   This is experimental code, not much tested, use with care.
    CImg<T>& load_magick(const char *const filename) {
      if (!filename)
        throw CImgArgumentException("CImg<%s>::load_magick() : Cannot load (null) filename.",
                                    pixel_type());
#ifdef cimg_use_magick
      Magick::Image image(filename);
      const unsigned int W = image.size().width(), H = image.size().height();
      switch (image.type()) {
      case Magick::PaletteMatteType :
      case Magick::TrueColorMatteType :
      case Magick::ColorSeparationType : {
        assign(W,H,1,4);
        T *rdata = ptr(0,0,0,0), *gdata = ptr(0,0,0,1), *bdata = ptr(0,0,0,2), *adata = ptr(0,0,0,3);
        Magick::PixelPacket *pixels = image.getPixels(0,0,W,H);
        for (unsigned int off = W*H; off; --off) {
          *(rdata++) = (T)(pixels->red);
          *(gdata++) = (T)(pixels->green);
          *(bdata++) = (T)(pixels->blue);
          *(adata++) = (T)(pixels->opacity);
          ++pixels;
        }
      } break;
      case Magick::PaletteType :
      case Magick::TrueColorType : {
        assign(W,H,1,3);
        T *rdata = ptr(0,0,0,0), *gdata = ptr(0,0,0,1), *bdata = ptr(0,0,0,2);
        Magick::PixelPacket *pixels = image.getPixels(0,0,W,H);
        for (unsigned int off = W*H; off; --off) {
          *(rdata++) = (T)(pixels->red);
          *(gdata++) = (T)(pixels->green);
          *(bdata++) = (T)(pixels->blue);
          ++pixels;
        }
      } break;
      case Magick::GrayscaleMatteType : {
        assign(W,H,1,2);
        T *data = ptr(0,0,0,0), *adata = ptr(0,0,0,1);
        Magick::PixelPacket *pixels = image.getPixels(0,0,W,H);
        for (unsigned int off = W*H; off; --off) {
          *(data++) = (T)(pixels->red);
          *(adata++) = (T)(pixels->opacity);
          ++pixels;
        }
      } break;
      default : {
        assign(W,H,1,1);
        T *data = ptr(0,0,0,0);
        Magick::PixelPacket *pixels = image.getPixels(0,0,W,H);
        for (unsigned int off = W*H; off; --off) {
          *(data++) = (T)(pixels->red);
          ++pixels;
        }
      }
      }
#else
      throw CImgIOException("CImg<%s>::load_magick() : File '%s', Magick++ library has not been linked.",
                            pixel_type(),filename);
#endif
      return *this;
    }

    static CImg<T> get_load_magick(const char *const filename) {
      return CImg<T>().load_magick(filename);
    }

    //! Load an image from a PNG file.
    CImg<T>& load_png(const char *const filename) {
      return _load_png(0,filename);
    }

    static CImg<T> get_load_png(const char *const filename) {
      return CImg<T>().load_png(filename);
    }

    //! Load an image from a PNG file.
    CImg<T>& load_png(cimg_std::FILE *const file) {
      return _load_png(file,0);
    }

    static CImg<T> get_load_png(cimg_std::FILE *const file) {
      return CImg<T>().load_png(file);
    }

    // (Note : Most of this function has been written by Eric Fausett)
    CImg<T>& _load_png(cimg_std::FILE *const file, const char *const filename) {
      if (!filename && !file)
        throw CImgArgumentException("CImg<%s>::load_png() : Cannot load (null) filename.",
                                    pixel_type());
#ifndef cimg_use_png
      if (file)
        throw CImgIOException("CImg<%s>::load_png() : File '(FILE*)' cannot be read without using libpng.",
                              pixel_type());
      else return load_other(filename);
#else
      // Open file and check for PNG validity
      const char *volatile nfilename = filename; // two 'volatile' here to remove a g++ warning due to 'setjmp'.
      cimg_std::FILE *volatile nfile = file?file:cimg::fopen(nfilename,"rb");

      unsigned char pngCheck[8];
      cimg::fread(pngCheck,8,(cimg_std::FILE*)nfile);
      if (png_sig_cmp(pngCheck,0,8)) {
        if (!file) cimg::fclose(nfile);
        throw CImgIOException("CImg<%s>::load_png() : File '%s' is not a valid PNG file.",
                              pixel_type(),nfilename?nfilename:"(FILE*)");
      }

      // Setup PNG structures for read
      png_voidp user_error_ptr = 0;
      png_error_ptr user_error_fn = 0, user_warning_fn = 0;
      png_structp png_ptr = png_create_read_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<%s>::load_png() : File '%s', trouble initializing 'png_ptr' data structure.",
                              pixel_type(),nfilename?nfilename:"(FILE*)");
      }
      png_infop info_ptr = png_create_info_struct(png_ptr);
      if (!info_ptr) {
        if (!file) cimg::fclose(nfile);
        png_destroy_read_struct(&png_ptr,(png_infopp)0,(png_infopp)0);
        throw CImgIOException("CImg<%s>::load_png() : File '%s', trouble initializing 'info_ptr' data structure.",
                              pixel_type(),nfilename?nfilename:"(FILE*)");
      }
      png_infop end_info = png_create_info_struct(png_ptr);
      if (!end_info) {
        if (!file) cimg::fclose(nfile);
        png_destroy_read_struct(&png_ptr,&info_ptr,(png_infopp)0);
        throw CImgIOException("CImg<%s>::load_png() : File '%s', trouble initializing 'end_info' data structure.",
                              pixel_type(),nfilename?nfilename:"(FILE*)");
      }

      // Error handling callback for png file reading
      if (setjmp(png_jmpbuf(png_ptr))) {
        if (!file) cimg::fclose((cimg_std::FILE*)nfile);
        png_destroy_read_struct(&png_ptr, &end_info, (png_infopp)0);
        throw CImgIOException("CImg<%s>::load_png() : File '%s', unknown fatal error.",
                              pixel_type(),nfilename?nfilename:"(FILE*)");
      }
      png_init_io(png_ptr, nfile);
      png_set_sig_bytes(png_ptr, 8);

      // Get PNG Header Info up to data block
      png_read_info(png_ptr,info_ptr);
      png_uint_32 W, H;
      int bit_depth, color_type, interlace_type;
      png_get_IHDR(png_ptr,info_ptr,&W,&H,&bit_depth,&color_type,&interlace_type,int_p_NULL,int_p_NULL);
      int new_bit_depth = bit_depth;
      int new_color_type = color_type;

      // Transforms to unify image data
      if (new_color_type == PNG_COLOR_TYPE_PALETTE){
        png_set_palette_to_rgb(png_ptr);
        new_color_type -= PNG_COLOR_MASK_PALETTE;
        new_bit_depth = 8;
      }
      if (new_color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8){
        png_set_gray_1_2_4_to_8(png_ptr);
        new_bit_depth = 8;
      }
      if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS))
        png_set_tRNS_to_alpha(png_ptr);
      if (new_color_type == PNG_COLOR_TYPE_GRAY || new_color_type == PNG_COLOR_TYPE_GRAY_ALPHA){
        png_set_gray_to_rgb(png_ptr);
        new_color_type |= PNG_COLOR_MASK_COLOR;
      }
      if (new_color_type == PNG_COLOR_TYPE_RGB)
        png_set_filler(png_ptr, 0xffffU, PNG_FILLER_AFTER);
      png_read_update_info(png_ptr,info_ptr);
      if (!(new_bit_depth==8 || new_bit_depth==16)) {
        if (!file) cimg::fclose(nfile);
        png_destroy_read_struct(&png_ptr, &end_info, (png_infopp)0);
        throw CImgIOException("CImg<%s>::load_png() : File '%s', wrong bit coding (bit_depth=%u)",
                              pixel_type(),nfilename?nfilename:"(FILE*)",new_bit_depth);
      }
      const int byte_depth = new_bit_depth>>3;

      // Allocate Memory for Image Read
      png_bytep *imgData = new png_bytep[H];
      for (unsigned int row = 0; row<H; ++row) imgData[row] = new png_byte[byte_depth*4*W];
      png_read_image(png_ptr,imgData);
      png_read_end(png_ptr,end_info);

      // Read pixel data
      if (!(new_color_type==PNG_COLOR_TYPE_RGB || new_color_type==PNG_COLOR_TYPE_RGB_ALPHA)) {
        if (!file) cimg::fclose(nfile);
        png_destroy_read_struct(&png_ptr,&end_info,(png_infopp)0);
        throw CImgIOException("CImg<%s>::load_png() : File '%s', wrong color coding (new_color_type=%u)",
                              pixel_type(),nfilename?nfilename:"(FILE*)",new_color_type);
      }
      const bool no_alpha_channel = (new_color_type==PNG_COLOR_TYPE_RGB);
      assign(W,H,1,no_alpha_channel?3:4);
      T *ptr1 = ptr(0,0,0,0), *ptr2 = ptr(0,0,0,1), *ptr3 = ptr(0,0,0,2), *ptr4 = ptr(0,0,0,3);
      switch (new_bit_depth) {
      case 8 : {
        cimg_forY(*this,y){
          const unsigned char *ptrs = (unsigned char*)imgData[y];
          cimg_forX(*this,x){
            *(ptr1++) = (T)*(ptrs++);
            *(ptr2++) = (T)*(ptrs++);
            *(ptr3++) = (T)*(ptrs++);
            if (no_alpha_channel) ++ptrs; else *(ptr4++) = (T)*(ptrs++);
          }
        }
      } break;
      case 16 : {
        cimg_forY(*this,y){
          const unsigned short *ptrs = (unsigned short*)(imgData[y]);
          if (!cimg::endianness()) cimg::invert_endianness(ptrs,4*width);
          cimg_forX(*this,x){
            *(ptr1++) = (T)*(ptrs++);
            *(ptr2++) = (T)*(ptrs++);
            *(ptr3++) = (T)*(ptrs++);
            if (no_alpha_channel) ++ptrs; else *(ptr4++) = (T)*(ptrs++);
          }
        }
      } break;
      }
      png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);

      // Deallocate Image Read Memory
      cimg_forY(*this,n) delete[] imgData[n];
      delete[] imgData;
      if (!file) cimg::fclose(nfile);
      return *this;
#endif
    }

    //! Load an image from a PNM file.
    CImg<T>& load_pnm(const char *const filename) {
      return _load_pnm(0,filename);
    }

    static CImg<T> get_load_pnm(const char *const filename) {
      return CImg<T>().load_pnm(filename);
    }

    //! Load an image from a PNM file.
    CImg<T>& load_pnm(cimg_std::FILE *const file) {
      return _load_pnm(file,0);
    }

    static CImg<T> get_load_pnm(cimg_std::FILE *const file) {
      return CImg<T>().load_pnm(file);
    }

    CImg<T>& _load_pnm(cimg_std::FILE *const file, const char *const filename) {
      if (!filename && !file)
        throw CImgArgumentException("CImg<%s>::load_pnm() : Cannot load (null) filename.",
                                    pixel_type());
      cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"rb");
      unsigned int ppm_type, W, H, colormax = 255;
      char item[1024] = { 0 };
      int err, rval, gval, bval;
      const int cimg_iobuffer = 12*1024*1024;
      while ((err=cimg_std::fscanf(nfile,"%1023[^\n]",item))!=EOF && (item[0]=='#' || !err)) cimg_std::fgetc(nfile);
      if (cimg_std::sscanf(item," P%u",&ppm_type)!=1) {
        if (!file) cimg::fclose(nfile);
        throw CImgIOException("CImg<%s>::load_pnm() : File '%s', PNM header 'P?' not found.",
                              pixel_type(),filename?filename:"(FILE*)");
      }
      while ((err=cimg_std::fscanf(nfile," %1023[^\n]",item))!=EOF && (item[0]=='#' || !err)) cimg_std::fgetc(nfile);
      if ((err=cimg_std::sscanf(item," %u %u %u",&W,&H,&colormax))<2) {
        if (!file) cimg::fclose(nfile);
        throw CImgIOException("CImg<%s>::load_pnm() : File '%s', WIDTH and HEIGHT fields are not defined in PNM header.",
                              pixel_type(),filename?filename:"(FILE*)");
      }
      if (err==2) {
        while ((err=cimg_std::fscanf(nfile," %1023[^\n]",item))!=EOF && (item[0]=='#' || !err)) cimg_std::fgetc(nfile);
        if (cimg_std::sscanf(item,"%u",&colormax)!=1)
          cimg::warn("CImg<%s>::load_pnm() : File '%s', COLORMAX field is not defined in PNM header.",
                     pixel_type(),filename?filename:"(FILE*)");
      }
      cimg_std::fgetc(nfile);
      assign();

      switch (ppm_type) {
      case 2 : { // Grey Ascii
        assign(W,H,1,1);
        T* rdata = data;
        cimg_foroff(*this,off) { if (cimg_std::fscanf(nfile,"%d",&rval)>0) *(rdata++) = (T)rval; else break; }
      } break;
      case 3 : { // Color Ascii
        assign(W,H,1,3);
        T *rdata = ptr(0,0,0,0), *gdata = ptr(0,0,0,1), *bdata = ptr(0,0,0,2);
        cimg_forXY(*this,x,y) {
          if (cimg_std::fscanf(nfile,"%d %d %d",&rval,&gval,&bval)==3) { *(rdata++) = (T)rval; *(gdata++) = (T)gval; *(bdata++) = (T)bval; }
          else break;
        }
      } break;
      case 5 : { // Grey Binary
        if (colormax<256) { // 8 bits
          CImg<ucharT> raw;
          assign(W,H,1,1);
          T *ptrd = ptr(0,0,0,0);
          for (int toread = (int)size(); toread>0; ) {
            raw.assign(cimg::min(toread,cimg_iobuffer));
            cimg::fread(raw.data,raw.width,nfile);
            toread-=raw.width;
            const unsigned char *ptrs = raw.data;
            for (unsigned int off = raw.width; off; --off) *(ptrd++) = (T)*(ptrs++);
          }
        } else { // 16 bits
          CImg<ushortT> raw;
          assign(W,H,1,1);
          T *ptrd = ptr(0,0,0,0);
          for (int toread = (int)size(); toread>0; ) {
            raw.assign(cimg::min(toread,cimg_iobuffer/2));
            cimg::fread(raw.data,raw.width,nfile);
            if (!cimg::endianness()) cimg::invert_endianness(raw.data,raw.width);
            toread-=raw.width;
            const unsigned short *ptrs = raw.data;
            for (unsigned int off = raw.width; off; --off) *(ptrd++) = (T)*(ptrs++);
          }
        }
      } break;
      case 6 : { // Color Binary
        if (colormax<256) { // 8 bits
          CImg<ucharT> raw;
          assign(W,H,1,3);
          T
            *ptr_r = ptr(0,0,0,0),
            *ptr_g = ptr(0,0,0,1),
            *ptr_b = ptr(0,0,0,2);
          for (int toread = (int)size(); toread>0; ) {
            raw.assign(cimg::min(toread,cimg_iobuffer));
            cimg::fread(raw.data,raw.width,nfile);
            toread-=raw.width;
            const unsigned char *ptrs = raw.data;
            for (unsigned int off = raw.width/3; off; --off) {
              *(ptr_r++) = (T)*(ptrs++);
              *(ptr_g++) = (T)*(ptrs++);
              *(ptr_b++) = (T)*(ptrs++);
            }
          }
        } else { // 16 bits
          CImg<ushortT> raw;
          assign(W,H,1,3);
          T
            *ptr_r = ptr(0,0,0,0),
            *ptr_g = ptr(0,0,0,1),
            *ptr_b = ptr(0,0,0,2);
          for (int toread = (int)size(); toread>0; ) {
            raw.assign(cimg::min(toread,cimg_iobuffer/2));
            cimg::fread(raw.data,raw.width,nfile);
            if (!cimg::endianness()) cimg::invert_endianness(raw.data,raw.width);
            toread-=raw.width;
            const unsigned short *ptrs = raw.data;
            for (unsigned int off = raw.width/3; off; --off) {
              *(ptr_r++) = (T)*(ptrs++);
              *(ptr_g++) = (T)*(ptrs++);
              *(ptr_b++) = (T)*(ptrs++);
            }
          }
        }
      } break;
      default :
        if (!file) cimg::fclose(nfile);
        throw CImgIOException("CImg<%s>::load_pnm() : File '%s', PPM type 'P%d' not supported.",
                              pixel_type(),filename?filename:"(FILE*)",ppm_type);
      }
      if (!file) cimg::fclose(nfile);
      return *this;
    }

    //! Load an image from a RGB file.
    CImg<T>& load_rgb(const char *const filename, const unsigned int dimw, const unsigned int dimh=1) {
      return _load_rgb(0,filename,dimw,dimh);
    }

    static CImg<T> get_load_rgb(const char *const filename, const unsigned int dimw, const unsigned int dimh=1) {
      return CImg<T>().load_rgb(filename,dimw,dimh);
    }

    //! Load an image from a RGB file.
    CImg<T>& load_rgb(cimg_std::FILE *const file, const unsigned int dimw, const unsigned int dimh=1) {
      return _load_rgb(file,0,dimw,dimh);
    }

    static CImg<T> get_load_rgb(cimg_std::FILE *const file, const unsigned int dimw, const unsigned int dimh=1) {
      return CImg<T>().load_rgb(file,dimw,dimh);
    }

    CImg<T>& _load_rgb(cimg_std::FILE *const file, const char *const filename, const unsigned int dimw, const unsigned int dimh) {
      if (!filename && !file)
        throw CImgArgumentException("CImg<%s>::load_rgb() : Cannot load (null) filename.",
                                    pixel_type());
      if (!dimw || !dimh) return assign();
      const int cimg_iobuffer = 12*1024*1024;
      cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"rb");
      CImg<ucharT> raw;
      assign(dimw,dimh,1,3);
      T
        *ptr_r = ptr(0,0,0,0),
        *ptr_g = ptr(0,0,0,1),
        *ptr_b = ptr(0,0,0,2);
      for (int toread = (int)size(); toread>0; ) {
        raw.assign(cimg::min(toread,cimg_iobuffer));
        cimg::fread(raw.data,raw.width,nfile);
        toread-=raw.width;
        const unsigned char *ptrs = raw.data;
        for (unsigned int off = raw.width/3; off; --off) {
          *(ptr_r++) = (T)*(ptrs++);
          *(ptr_g++) = (T)*(ptrs++);
          *(ptr_b++) = (T)*(ptrs++);
        }
      }
      if (!file) cimg::fclose(nfile);
      return *this;
    }

    //! Load an image from a RGBA file.
    CImg<T>& load_rgba(const char *const filename, const unsigned int dimw, const unsigned int dimh=1) {
      return _load_rgba(0,filename,dimw,dimh);
    }

    static CImg<T> get_load_rgba(const char *const filename, const unsigned int dimw, const unsigned int dimh=1) {
      return CImg<T>().load_rgba(filename,dimw,dimh);
    }

    //! Load an image from a RGBA file.
    CImg<T>& load_rgba(cimg_std::FILE *const file, const unsigned int dimw, const unsigned int dimh=1) {
      return _load_rgba(file,0,dimw,dimh);
    }

    static CImg<T> get_load_rgba(cimg_std::FILE *const file, const unsigned int dimw, const unsigned int dimh=1) {
      return CImg<T>().load_rgba(file,dimw,dimh);
    }

    CImg<T>& _load_rgba(cimg_std::FILE *const file, const char *const filename, const unsigned int dimw, const unsigned int dimh) {
      if (!filename && !file)
        throw CImgArgumentException("CImg<%s>::load_rgba() : Cannot load (null) filename.",
                                    pixel_type());
      if (!dimw || !dimh) return assign();
      const int cimg_iobuffer = 12*1024*1024;
      cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"rb");
      CImg<ucharT> raw;
      assign(dimw,dimh,1,4);
      T
        *ptr_r = ptr(0,0,0,0),
        *ptr_g = ptr(0,0,0,1),
        *ptr_b = ptr(0,0,0,2),
        *ptr_a = ptr(0,0,0,3);
      for (int toread = (int)size(); toread>0; ) {
        raw.assign(cimg::min(toread,cimg_iobuffer));
        cimg::fread(raw.data,raw.width,nfile);
        toread-=raw.width;
        const unsigned char *ptrs = raw.data;
        for (unsigned int off = raw.width/4; off; --off) {
          *(ptr_r++) = (T)*(ptrs++);
          *(ptr_g++) = (T)*(ptrs++);
          *(ptr_b++) = (T)*(ptrs++);
          *(ptr_a++) = (T)*(ptrs++);
        }
      }
      if (!file) cimg::fclose(nfile);
      return *this;
    }

    //! Load an image from a TIFF file.
    CImg<T>& load_tiff(const char *const filename,
                       const unsigned int first_frame=0, const unsigned int last_frame=~0U,
                       const unsigned int step_frame=1) {
      if (!filename)
        throw CImgArgumentException("CImg<%s>::load_tiff() : Cannot load (null) filename.",
                                    pixel_type());
      const unsigned int
        nfirst_frame = first_frame<last_frame?first_frame:last_frame,
        nstep_frame = step_frame?step_frame:1;
      unsigned int nlast_frame = first_frame<last_frame?last_frame:first_frame;

#ifndef cimg_use_tiff
      if (nfirst_frame || nlast_frame!=~0U || nstep_frame>1)
        throw CImgArgumentException("CImg<%s>::load_tiff() : File '%s', reading sub-images from a tiff file requires the use of libtiff.\n"
                                    "('cimg_use_tiff' must be defined).",
                                    pixel_type(),filename);
      return load_other(filename);
#else
      TIFF *tif = TIFFOpen(filename,"r");
      if (tif) {
        unsigned int nb_images = 0;
        do ++nb_images; while (TIFFReadDirectory(tif));
        if (nfirst_frame>=nb_images || (nlast_frame!=~0U && nlast_frame>=nb_images))
          cimg::warn("CImg<%s>::load_tiff() : File '%s' contains %u image(s), specified frame range is [%u,%u] (step %u).",
                     pixel_type(),filename,nb_images,nfirst_frame,nlast_frame,nstep_frame);
        if (nfirst_frame>=nb_images) return assign();
        if (nlast_frame>=nb_images) nlast_frame = nb_images-1;
        TIFFSetDirectory(tif,0);
        CImg<T> frame;
        for (unsigned int l = nfirst_frame; l<=nlast_frame; l+=nstep_frame) {
          frame._load_tiff(tif,l);
          if (l==nfirst_frame) assign(frame.width,frame.height,1+(nlast_frame-nfirst_frame)/nstep_frame,frame.dim);
          if (frame.width>width || frame.height>height || frame.dim>dim)
            resize(cimg::max(frame.width,width),cimg::max(frame.height,height),-100,cimg::max(frame.dim,dim),0);
          draw_image(0,0,(l-nfirst_frame)/nstep_frame,frame);
        }
        TIFFClose(tif);
      } else throw CImgException("CImg<%s>::load_tiff() : File '%s' cannot be opened.",
                                 pixel_type(),filename);
      return *this;
#endif
    }

    static CImg<T> get_load_tiff(const char *const filename,
                                 const unsigned int first_frame=0, const unsigned int last_frame=~0U,
                                 const unsigned int step_frame=1) {
      return CImg<T>().load_tiff(filename,first_frame,last_frame,step_frame);
    }

    // (Original contribution by Jerome Boulanger).
#ifdef cimg_use_tiff
    CImg<T>& _load_tiff(TIFF *tif, const unsigned int directory) {
      if (!TIFFSetDirectory(tif,directory)) return assign();
      uint16 samplesperpixel, bitspersample;
      uint32 nx,ny;
      const char *const filename = TIFFFileName(tif);
      TIFFGetField(tif,TIFFTAG_IMAGEWIDTH,&nx);
      TIFFGetField(tif,TIFFTAG_IMAGELENGTH,&ny);
      TIFFGetField(tif,TIFFTAG_SAMPLESPERPIXEL,&samplesperpixel);
      if (samplesperpixel!=1 && samplesperpixel!=3 && samplesperpixel!=4) {
        cimg::warn("CImg<%s>::load_tiff() : File '%s', unknow value for tag : TIFFTAG_SAMPLESPERPIXEL, will force it to 1.",
                   pixel_type(),filename);
        samplesperpixel = 1;
      }
      TIFFGetFieldDefaulted(tif,TIFFTAG_BITSPERSAMPLE,&bitspersample);
      assign(nx,ny,1,samplesperpixel);
      if (bitspersample!=8 || !(samplesperpixel==3 || samplesperpixel==4)) {
        uint16 photo, config;
        TIFFGetField(tif,TIFFTAG_PLANARCONFIG,&config);
        TIFFGetField(tif,TIFFTAG_PHOTOMETRIC,&photo);
        if (TIFFIsTiled(tif)) {
          uint32 tw, th;
          TIFFGetField(tif,TIFFTAG_TILEWIDTH,&tw);
          TIFFGetField(tif,TIFFTAG_TILELENGTH,&th);
          if (config==PLANARCONFIG_CONTIG) switch (bitspersample) {
            case 8 : {
              unsigned char *buf = (unsigned char*)_TIFFmalloc(TIFFTileSize(tif));
              if (buf) {
                for (unsigned int row = 0; row<ny; row+=th)
                  for (unsigned int col = 0; col<nx; col+=tw) {
                    if (TIFFReadTile(tif,buf,col,row,0,0)<0) {
                      _TIFFfree(buf); TIFFClose(tif);
                      throw CImgException("CImg<%s>::load_tiff() : File '%s', an error occure while reading a tile.",
                                          pixel_type(),filename);
                    } else {
                      unsigned char *ptr = buf;
                      for (unsigned int rr = row; rr<cimg::min((unsigned int)(row+th),(unsigned int)ny); ++rr)
                        for (unsigned int cc = col; cc<cimg::min((unsigned int)(col+tw),(unsigned int)nx); ++cc)
                          for (unsigned int vv = 0; vv<samplesperpixel; ++vv)
                            (*this)(cc,rr,vv) = (T)(float)(ptr[(rr-row)*th*samplesperpixel + (cc-col)*samplesperpixel + vv]);
                    }
                  }
                _TIFFfree(buf);
              }
            } break;
            case 16 : {
              unsigned short *buf = (unsigned short*)_TIFFmalloc(TIFFTileSize(tif));
              if (buf) {
                for (unsigned int row = 0; row<ny; row+=th)
                  for (unsigned int col = 0; col<nx; col+=tw) {
                    if (TIFFReadTile(tif,buf,col,row,0,0)<0) {
                      _TIFFfree(buf); TIFFClose(tif);
                      throw CImgException("CImg<%s>::load_tiff() : File '%s', an error occure while reading a tile.",
                                          pixel_type(),filename);
                    } else {
                      unsigned short *ptr = buf;
                      for (unsigned int rr = row; rr<cimg::min((unsigned int)(row+th),(unsigned int)ny); ++rr)
                        for (unsigned int cc = col; cc<cimg::min((unsigned int)(col+tw),(unsigned int)nx); ++cc)
                          for (unsigned int vv = 0; vv<samplesperpixel; ++vv)
                            (*this)(cc,rr,vv) = (T)(float)(ptr[(rr-row)*th*samplesperpixel + (cc-col)*samplesperpixel + vv]);
                    }
                  }
                _TIFFfree(buf);
              }
            } break;
            case 32 : {
              float *buf = (float*)_TIFFmalloc(TIFFTileSize(tif));
              if (buf) {
                for (unsigned int row = 0; row<ny; row+=th)
                  for (unsigned int col = 0; col<nx; col+=tw) {
                    if (TIFFReadTile(tif,buf,col,row,0,0)<0) {
                      _TIFFfree(buf); TIFFClose(tif);
                      throw CImgException("CImg<%s>::load_tiff() : File '%s', an error occure while reading a tile.",
                                          pixel_type(),filename);
                    } else {
                      float *ptr = buf;
                      for (unsigned int rr = row; rr<cimg::min((unsigned int)(row+th),(unsigned int)ny); ++rr)
                        for (unsigned int cc = col; cc<cimg::min((unsigned int)(col+tw),(unsigned int)nx); ++cc)
                          for (unsigned int vv = 0; vv<samplesperpixel; ++vv)
                            (*this)(cc,rr,vv) = (T)(float)(ptr[(rr-row)*th*samplesperpixel + (cc-col)*samplesperpixel + vv]);
                    }
                  }
                _TIFFfree(buf);
              }
            } break;
            } else switch (bitspersample) {
            case 8 : {
              unsigned char *buf = (unsigned char*)_TIFFmalloc(TIFFTileSize(tif));
              if (buf) {
                for (unsigned int vv = 0; vv<samplesperpixel; ++vv)
                  for (unsigned int row = 0; row<ny; row+=th)
                    for (unsigned int col = 0; col<nx; col+=tw) {
                      if (TIFFReadTile(tif,buf,col,row,0,vv)<0) {
                        _TIFFfree(buf); TIFFClose(tif);
                        throw CImgException("CImg<%s>::load_tiff() : File '%s', an error occure while reading a tile.",
                                            pixel_type(),filename);
                      } else {
                        unsigned char *ptr = buf;
                        for (unsigned int rr = row; rr<cimg::min((unsigned int)(row+th),(unsigned int)ny); ++rr)
                          for (unsigned int cc = col; cc<cimg::min((unsigned int)(col+tw),(unsigned int)nx); ++cc)
                            (*this)(cc,rr,vv) = (T)(float)*(ptr++);
                      }
                    }
                _TIFFfree(buf);
              }
            } break;
            case 16 : {
              unsigned short *buf = (unsigned short*)_TIFFmalloc(TIFFTileSize(tif));
              if (buf) {
                for (unsigned int vv = 0; vv<samplesperpixel; ++vv)
                  for (unsigned int row = 0; row<ny; row+=th)
                    for (unsigned int col = 0; col<nx; col+=tw) {
                      if (TIFFReadTile(tif,buf,col,row,0,vv)<0) {
                        _TIFFfree(buf); TIFFClose(tif);
                        throw CImgException("CImg<%s>::load_tiff() : File '%s', an error occure while reading a tile.",
                                            pixel_type(),filename);
                      } else {
                        unsigned short *ptr = buf;
                        for (unsigned int rr = row; rr<cimg::min((unsigned int)(row+th),(unsigned int)ny); ++rr)
                          for (unsigned int cc = col; cc<cimg::min((unsigned int)(col+tw),(unsigned int)nx); ++cc)
                            (*this)(cc,rr,vv) = (T)(float)*(ptr++);
                      }
                    }
                _TIFFfree(buf);
              }
            } break;
            case 32 : {
              float *buf = (float*)_TIFFmalloc(TIFFTileSize(tif));
              if (buf) {
                for (unsigned int vv = 0; vv<samplesperpixel; ++vv)
                  for (unsigned int row = 0; row<ny; row+=th)
                    for (unsigned int col = 0; col<nx; col+=tw) {
                      if (TIFFReadTile(tif,buf,col,row,0,vv)<0) {
                        _TIFFfree(buf); TIFFClose(tif);
                        throw CImgException("CImg<%s>::load_tiff() : File '%s', an error occure while reading a tile.",
                                            pixel_type(),filename);
                      } else {
                        float *ptr = buf;
                        for (unsigned int rr = row; rr<cimg::min((unsigned int)(row+th),(unsigned int)ny); ++rr)
                          for (unsigned int cc = col; cc<cimg::min((unsigned int)(col+tw),(unsigned int)nx); ++cc)
                            (*this)(cc,rr,vv) = (T)(float)*(ptr++);
                      }
                    }
                _TIFFfree(buf);
              }
            } break;
            }
        } else {
          if (config==PLANARCONFIG_CONTIG) switch (bitspersample) {
            case 8 : {
              unsigned char *buf = (unsigned char*)_TIFFmalloc(TIFFStripSize(tif));
              if (buf) {
                uint32 row, rowsperstrip = (uint32)-1;
                TIFFGetField(tif,TIFFTAG_ROWSPERSTRIP,&rowsperstrip);
                for (row = 0; row<ny; row+= rowsperstrip) {
                  uint32 nrow = (row+rowsperstrip>ny?ny-row:rowsperstrip);
                  tstrip_t strip = TIFFComputeStrip(tif, row, 0);
                  if ((TIFFReadEncodedStrip(tif,strip,buf,-1))<0) {
                    _TIFFfree(buf); TIFFClose(tif);
                    throw CImgException("CImg<%s>::load_tiff() : File '%s', an error occure while reading a strip.",
                                        pixel_type(),filename);
                  }
                  unsigned char *ptr = buf;
                  for (unsigned int rr = 0; rr<nrow; ++rr)
                    for (unsigned int cc = 0; cc<nx; ++cc)
                      for (unsigned int vv = 0; vv<samplesperpixel; ++vv) (*this)(cc,row+rr,vv) = (T)(float)*(ptr++);
                }
                _TIFFfree(buf);
              }
            } break;
            case 16 : {
              unsigned short *buf = (unsigned short*)_TIFFmalloc(TIFFStripSize(tif));
              if (buf) {
                uint32 row, rowsperstrip = (uint32)-1;
                TIFFGetField(tif,TIFFTAG_ROWSPERSTRIP,&rowsperstrip);
                for (row = 0; row<ny; row+= rowsperstrip) {
                  uint32 nrow = (row+rowsperstrip>ny?ny-row:rowsperstrip);
                  tstrip_t strip = TIFFComputeStrip(tif, row, 0);
                  if ((TIFFReadEncodedStrip(tif,strip,buf,-1))<0) {
                    _TIFFfree(buf); TIFFClose(tif);
                    throw CImgException("CImg<%s>::load_tiff() : File '%s', error while reading a strip.",
                                        pixel_type(),filename);
                  }
                  unsigned short *ptr = buf;
                  for (unsigned int rr = 0; rr<nrow; ++rr)
                    for (unsigned int cc = 0; cc<nx; ++cc)
                      for (unsigned int vv = 0; vv<samplesperpixel; ++vv) (*this)(cc,row+rr,vv) = (T)(float)*(ptr++);
                }
                _TIFFfree(buf);
              }
            } break;
            case 32 : {
              float *buf = (float*)_TIFFmalloc(TIFFStripSize(tif));
              if (buf) {
                uint32 row, rowsperstrip = (uint32)-1;
                TIFFGetField(tif,TIFFTAG_ROWSPERSTRIP,&rowsperstrip);
                for (row = 0; row<ny; row+= rowsperstrip) {
                  uint32 nrow = (row+rowsperstrip>ny?ny-row:rowsperstrip);
                  tstrip_t strip = TIFFComputeStrip(tif, row, 0);
                  if ((TIFFReadEncodedStrip(tif,strip,buf,-1))<0) {
                    _TIFFfree(buf); TIFFClose(tif);
                    throw CImgException("CImg<%s>::load_tiff() : File '%s', error while reading a strip.",
                                        pixel_type(),filename);
                  }
                  float *ptr = buf;
                  for (unsigned int rr = 0; rr<nrow; ++rr)
                    for (unsigned int cc = 0; cc<nx; ++cc)
                      for (unsigned int vv = 0; vv<samplesperpixel; ++vv) (*this)(cc,row+rr,vv) = (T)(float)*(ptr++);
                }
                _TIFFfree(buf);
              }
            } break;
            } else switch (bitspersample){
            case 8 : {
              unsigned char *buf = (unsigned char*)_TIFFmalloc(TIFFStripSize(tif));
              if (buf) {
                uint32 row, rowsperstrip = (uint32)-1;
                TIFFGetField(tif,TIFFTAG_ROWSPERSTRIP,&rowsperstrip);
                for (unsigned int vv=0; vv<samplesperpixel; ++vv)
                  for (row = 0; row<ny; row+= rowsperstrip) {
                    uint32 nrow = (row+rowsperstrip>ny?ny-row:rowsperstrip);
                    tstrip_t strip = TIFFComputeStrip(tif, row, vv);
                    if ((TIFFReadEncodedStrip(tif,strip,buf,-1))<0) {
                      _TIFFfree(buf); TIFFClose(tif);
                      throw CImgException("CImg<%s>::load_tiff() : File '%s', an error occure while reading a strip.",
                                          pixel_type(),filename);
                    }
                    unsigned char *ptr = buf;
                    for (unsigned int rr = 0;rr<nrow; ++rr)
                      for (unsigned int cc = 0; cc<nx; ++cc)
                        (*this)(cc,row+rr,vv) = (T)(float)*(ptr++);
                  }
                _TIFFfree(buf);
              }
            } break;
            case 16 : {
              unsigned short *buf = (unsigned short*)_TIFFmalloc(TIFFStripSize(tif));
              if (buf) {
                uint32 row, rowsperstrip = (uint32)-1;
                TIFFGetField(tif,TIFFTAG_ROWSPERSTRIP,&rowsperstrip);
                for (unsigned int vv = 0; vv<samplesperpixel; ++vv)
                  for (row = 0; row<ny; row+= rowsperstrip) {
                    uint32 nrow = (row+rowsperstrip>ny?ny-row:rowsperstrip);
                    tstrip_t strip = TIFFComputeStrip(tif, row, vv);
                    if ((TIFFReadEncodedStrip(tif,strip,buf,-1))<0) {
                      _TIFFfree(buf); TIFFClose(tif);
                      throw CImgException("CImg<%s>::load_tiff() : File '%s', error while reading a strip.",
                                          pixel_type(),filename);
                    }
                    unsigned short *ptr = buf;
                    for (unsigned int rr = 0; rr<nrow; ++rr)
                      for (unsigned int cc = 0; cc<nx; ++cc)
                        (*this)(cc,row+rr,vv) = (T)(float)*(ptr++);
                  }
                _TIFFfree(buf);
              }
            } break;
            case 32 : {
              float *buf = (float*)_TIFFmalloc(TIFFStripSize(tif));
              if (buf) {
                uint32 row, rowsperstrip = (uint32)-1;
                TIFFGetField(tif,TIFFTAG_ROWSPERSTRIP,&rowsperstrip);
                for (unsigned int vv = 0; vv<samplesperpixel; ++vv)
                  for (row = 0; row<ny; row+= rowsperstrip) {
                    uint32 nrow = (row+rowsperstrip>ny?ny-row:rowsperstrip);
                    tstrip_t strip = TIFFComputeStrip(tif, row, vv);
                    if ((TIFFReadEncodedStrip(tif,strip,buf,-1))<0) {
                      _TIFFfree(buf); TIFFClose(tif);
                      throw CImgException("CImg<%s>::load_tiff() : File '%s', error while reading a strip.",
                                          pixel_type(),filename);
                    }
                    float *ptr = buf;
                    for (unsigned int rr = 0; rr<nrow; ++rr)  for (unsigned int cc = 0; cc<nx; ++cc)
                        (*this)(cc,row+rr,vv) = (T)(float)*(ptr++);
                  }
                _TIFFfree(buf);
              }
            } break;
            }
        }
      } else {
        uint32* raster = (uint32*)_TIFFmalloc(nx * ny * sizeof (uint32));
        if (!raster) {
          _TIFFfree(raster); TIFFClose(tif);
          throw CImgException("CImg<%s>::load_tiff() : File '%s', not enough memory for buffer allocation.",
                              pixel_type(),filename);
        }
        TIFFReadRGBAImage(tif,nx,ny,raster,0);
        switch (samplesperpixel) {
        case 1 : {
          cimg_forXY(*this,x,y) (*this)(x,y) = (T)(float)((raster[nx*(ny-1-y)+x]+ 128) / 257);
        } break;
        case 3 : {
          cimg_forXY(*this,x,y) {
            (*this)(x,y,0) = (T)(float)TIFFGetR(raster[nx*(ny-1-y)+x]);
            (*this)(x,y,1) = (T)(float)TIFFGetG(raster[nx*(ny-1-y)+x]);
            (*this)(x,y,2) = (T)(float)TIFFGetB(raster[nx*(ny-1-y)+x]);
          }
        } break;
        case 4 : {
          cimg_forXY(*this,x,y) {
            (*this)(x,y,0) = (T)(float)TIFFGetR(raster[nx*(ny-1-y)+x]);
            (*this)(x,y,1) = (T)(float)TIFFGetG(raster[nx*(ny-1-y)+x]);
            (*this)(x,y,2) = (T)(float)TIFFGetB(raster[nx*(ny-1-y)+x]);
            (*this)(x,y,3) = (T)(float)TIFFGetA(raster[nx*(ny-1-y)+x]);
          }
        } break;
        }
        _TIFFfree(raster);
      }
      return *this;
    }
#endif

    //! Load an image from an ANALYZE7.5/NIFTI file.
    CImg<T>& load_analyze(const char *const filename, float *const voxsize=0) {
      return _load_analyze(0,filename,voxsize);
    }

    static CImg<T> get_load_analyze(const char *const filename, float *const voxsize=0) {
      return CImg<T>().load_analyze(filename,voxsize);
    }

    //! Load an image from an ANALYZE7.5/NIFTI file.
    CImg<T>& load_analyze(cimg_std::FILE *const file, float *const voxsize=0) {
      return _load_analyze(file,0,voxsize);
    }

    static CImg<T> get_load_analyze(cimg_std::FILE *const file, float *const voxsize=0) {
      return CImg<T>().load_analyze(file,voxsize);
    }

    CImg<T>& _load_analyze(cimg_std::FILE *const file, const char *const filename, float *const voxsize=0) {
      if (!filename && !file)
        throw CImgArgumentException("CImg<%s>::load_analyze() : Cannot load (null) filename.",
                                    pixel_type());
      cimg_std::FILE *nfile_header = 0, *nfile = 0;
      if (!file) {
        char body[1024];
        const char *ext = cimg::split_filename(filename,body);
        if (!cimg::strcasecmp(ext,"hdr")) { // File is an Analyze header file.
          nfile_header = cimg::fopen(filename,"rb");
          cimg_std::sprintf(body+cimg::strlen(body),".img");
          nfile = cimg::fopen(body,"rb");
        } else if (!cimg::strcasecmp(ext,"img")) { // File is an Analyze data file.
          nfile = cimg::fopen(filename,"rb");
          cimg_std::sprintf(body+cimg::strlen(body),".hdr");
          nfile_header = cimg::fopen(body,"rb");
        } else nfile_header = nfile = cimg::fopen(filename,"rb"); // File is a Niftii file.
      } else nfile_header = nfile = file; // File is a Niftii file.
      if (!nfile || !nfile_header)
        throw CImgIOException("CImg<%s>::load_analyze() : File '%s', not recognized as an Analyze7.5 or NIFTI file.",
                              pixel_type(),filename?filename:"(FILE*)");

      // Read header.
      bool endian = false;
      unsigned int header_size;
      cimg::fread(&header_size,1,nfile_header);
      if (!header_size)
        throw CImgIOException("CImg<%s>::load_analyze() : File '%s', zero-sized header found.",
                              pixel_type(),filename?filename:"(FILE*)");
      if (header_size>=4096) { endian = true; cimg::invert_endianness(header_size); }
      unsigned char *header = new unsigned char[header_size];
      cimg::fread(header+4,header_size-4,nfile_header);
      if (!file && nfile_header!=nfile) cimg::fclose(nfile_header);
      if (endian) {
        cimg::invert_endianness((short*)(header+40),5);
        cimg::invert_endianness((short*)(header+70),1);
        cimg::invert_endianness((short*)(header+72),1);
        cimg::invert_endianness((float*)(header+76),4);
        cimg::invert_endianness((float*)(header+112),1);
      }
      unsigned short *dim = (unsigned short*)(header+40), dimx = 1, dimy = 1, dimz = 1, dimv = 1;
      if (!dim[0])
        cimg::warn("CImg<%s>::load_analyze() : File '%s', tells that image has zero dimensions.",
                   pixel_type(),filename?filename:"(FILE*)");
      if (dim[0]>4)
        cimg::warn("CImg<%s>::load_analyze() : File '%s', number of image dimension is %u, reading only the 4 first dimensions",
                   pixel_type(),filename?filename:"(FILE*)",dim[0]);
      if (dim[0]>=1) dimx = dim[1];
      if (dim[0]>=2) dimy = dim[2];
      if (dim[0]>=3) dimz = dim[3];
      if (dim[0]>=4) dimv = dim[4];
      float scalefactor = *(float*)(header+112); if (scalefactor==0) scalefactor=1;
      const unsigned short datatype = *(short*)(header+70);
      if (voxsize) {
        const float *vsize = (float*)(header+76);
        voxsize[0] = vsize[1]; voxsize[1] = vsize[2]; voxsize[2] = vsize[3];
      }
      delete[] header;

      // Read pixel data.
      assign(dimx,dimy,dimz,dimv);
      switch (datatype) {
      case 2 : {
        unsigned char *buffer = new unsigned char[dimx*dimy*dimz*dimv];
        cimg::fread(buffer,dimx*dimy*dimz*dimv,nfile);
        cimg_foroff(*this,off) data[off] = (T)(buffer[off]*scalefactor);
        delete[] buffer;
      } break;
      case 4 : {
        short *buffer = new short[dimx*dimy*dimz*dimv];
        cimg::fread(buffer,dimx*dimy*dimz*dimv,nfile);
        if (endian) cimg::invert_endianness(buffer,dimx*dimy*dimz*dimv);
        cimg_foroff(*this,off) data[off] = (T)(buffer[off]*scalefactor);
        delete[] buffer;
      } break;
      case 8 : {
        int *buffer = new int[dimx*dimy*dimz*dimv];
        cimg::fread(buffer,dimx*dimy*dimz*dimv,nfile);
        if (endian) cimg::invert_endianness(buffer,dimx*dimy*dimz*dimv);
        cimg_foroff(*this,off) data[off] = (T)(buffer[off]*scalefactor);
        delete[] buffer;
      } break;
      case 16 : {
        float *buffer = new float[dimx*dimy*dimz*dimv];
        cimg::fread(buffer,dimx*dimy*dimz*dimv,nfile);
        if (endian) cimg::invert_endianness(buffer,dimx*dimy*dimz*dimv);
        cimg_foroff(*this,off) data[off] = (T)(buffer[off]*scalefactor);
        delete[] buffer;
      } break;
      case 64 : {
        double *buffer = new double[dimx*dimy*dimz*dimv];
        cimg::fread(buffer,dimx*dimy*dimz*dimv,nfile);
        if (endian) cimg::invert_endianness(buffer,dimx*dimy*dimz*dimv);
        cimg_foroff(*this,off) data[off] = (T)(buffer[off]*scalefactor);
        delete[] buffer;
      } break;
      default :
        if (!file) cimg::fclose(nfile);
        throw CImgIOException("CImg<%s>::load_analyze() : File '%s', cannot read images with 'datatype = %d'",
                              pixel_type(),filename?filename:"(FILE*)",datatype);
      }
      if (!file) cimg::fclose(nfile);
      return *this;
    }

    //! Load an image (list) from a .cimg file.
    CImg<T>& load_cimg(const char *const filename, const char axis='z', const char align='p') {
      CImgList<T> list;
      list.load_cimg(filename);
      if (list.size==1) return list[0].transfer_to(*this);
      return assign(list.get_append(axis,align));
    }

    static CImg<T> get_load_cimg(const char *const filename, const char axis='z', const char align='p') {
      return CImg<T>().load_cimg(filename,axis,align);
    }

    //! Load an image (list) from a .cimg file.
    CImg<T>& load_cimg(cimg_std::FILE *const file, const char axis='z', const char align='p') {
      CImgList<T> list;
      list.load_cimg(file);
      if (list.size==1) return list[0].transfer_to(*this);
      return assign(list.get_append(axis,align));
    }

    static CImg<T> get_load_cimg(cimg_std::FILE *const file, const char axis='z', const char align='p') {
      return CImg<T>().load_cimg(file,axis,align);
    }

    //! Load a sub-image (list) from a .cimg file.
    CImg<T>& 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 v0,
                       const unsigned int x1, const unsigned int y1, const unsigned int z1, const unsigned int v1,
                       const char axis='z', const char align='p') {
      CImgList<T> list;
      list.load_cimg(filename,n0,n1,x0,y0,z0,v0,x1,y1,z1,v1);
      if (list.size==1) return list[0].transfer_to(*this);
      return assign(list.get_append(axis,align));
    }

    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 v0,
                                 const unsigned int x1, const unsigned int y1, const unsigned int z1, const unsigned int v1,
                                 const char axis='z', const char align='p') {
      return CImg<T>().load_cimg(filename,n0,n1,x0,y0,z0,v0,x1,y1,z1,v1,axis,align);
    }

    //! Load a sub-image (list) from a non-compressed .cimg file.
    CImg<T>& load_cimg(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 v0,
                       const unsigned int x1, const unsigned int y1, const unsigned int z1, const unsigned int v1,
                       const char axis='z', const char align='p') {
      CImgList<T> list;
      list.load_cimg(file,n0,n1,x0,y0,z0,v0,x1,y1,z1,v1);
      if (list.size==1) return list[0].transfer_to(*this);
      return assign(list.get_append(axis,align));
    }

    static CImg<T> get_load_cimg(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 v0,
                                 const unsigned int x1, const unsigned int y1, const unsigned int z1, const unsigned int v1,
                                 const char axis='z', const char align='p') {
      return CImg<T>().load_cimg(file,n0,n1,x0,y0,z0,v0,x1,y1,z1,v1,axis,align);
    }

    //! Load an image from an INRIMAGE-4 file.
    CImg<T>& load_inr(const char *const filename, float *const voxsize=0) {
      return _load_inr(0,filename,voxsize);
    }

    static CImg<T> get_load_inr(const char *const filename, float *const voxsize=0) {
      return CImg<T>().load_inr(filename,voxsize);
    }

    //! Load an image from an INRIMAGE-4 file.
    CImg<T>& load_inr(cimg_std::FILE *const file, float *const voxsize=0) {
      return _load_inr(file,0,voxsize);
    }

    static CImg<T> get_load_inr(cimg_std::FILE *const file, float *voxsize=0) {
      return CImg<T>().load_inr(file,voxsize);
    }

    // Load an image from an INRIMAGE-4 file (internal).
    static void _load_inr_header(cimg_std::FILE *file, int out[8], float *const voxsize) {
      char item[1024], tmp1[64], tmp2[64];
      out[0] = cimg_std::fscanf(file,"%63s",item);
      out[0] = out[1] = out[2] = out[3] = out[5] = 1; out[4] = out[6] = out[7] = -1;
      if(cimg::strncasecmp(item,"#INRIMAGE-4#{",13)!=0)
        throw CImgIOException("CImg<%s>::load_inr() : File does not appear to be a valid INR file.\n"
                              "(INRIMAGE-4 identifier not found)",
                              pixel_type());
      while (cimg_std::fscanf(file," %63[^\n]%*c",item)!=EOF && cimg::strncmp(item,"##}",3)) {
        cimg_std::sscanf(item," XDIM%*[^0-9]%d",out);
        cimg_std::sscanf(item," YDIM%*[^0-9]%d",out+1);
        cimg_std::sscanf(item," ZDIM%*[^0-9]%d",out+2);
        cimg_std::sscanf(item," VDIM%*[^0-9]%d",out+3);
        cimg_std::sscanf(item," PIXSIZE%*[^0-9]%d",out+6);
        if (voxsize) {
          cimg_std::sscanf(item," VX%*[^0-9.+-]%f",voxsize);
          cimg_std::sscanf(item," VY%*[^0-9.+-]%f",voxsize+1);
          cimg_std::sscanf(item," VZ%*[^0-9.+-]%f",voxsize+2);
        }
        if (cimg_std::sscanf(item," CPU%*[ =]%s",tmp1)) out[7]=cimg::strncasecmp(tmp1,"sun",3)?0:1;
        switch (cimg_std::sscanf(item," TYPE%*[ =]%s %s",tmp1,tmp2)) {
        case 0 : break;
        case 2 : out[5] = cimg::strncasecmp(tmp1,"unsigned",8)?1:0; cimg_std::strcpy(tmp1,tmp2);
        case 1 :
          if (!cimg::strncasecmp(tmp1,"int",3)   || !cimg::strncasecmp(tmp1,"fixed",5))  out[4] = 0;
          if (!cimg::strncasecmp(tmp1,"float",5) || !cimg::strncasecmp(tmp1,"double",6)) out[4] = 1;
          if (!cimg::strncasecmp(tmp1,"packed",6))                                       out[4] = 2;
          if (out[4]>=0) break;
        default :
          throw CImgIOException("cimg::inr_header_read() : Invalid TYPE '%s'",tmp2);
        }
      }
      if(out[0]<0 || out[1]<0 || out[2]<0 || out[3]<0)
        throw CImgIOException("CImg<%s>::load_inr() : Bad dimensions in .inr file = ( %d , %d , %d , %d )",
                              pixel_type(),out[0],out[1],out[2],out[3]);
      if(out[4]<0 || out[5]<0)
        throw CImgIOException("CImg<%s>::load_inr() : TYPE is not fully defined",
                              pixel_type());
      if(out[6]<0)
        throw CImgIOException("CImg<%s>::load_inr() : PIXSIZE is not fully defined",
                              pixel_type());
      if(out[7]<0)
        throw CImgIOException("CImg<%s>::load_inr() : Big/Little Endian coding type is not defined",
                              pixel_type());
    }

    CImg<T>& _load_inr(cimg_std::FILE *const file, const char *const filename, float *const voxsize) {
#define _cimg_load_inr_case(Tf,sign,pixsize,Ts) \
     if (!loaded && fopt[6]==pixsize && fopt[4]==Tf && fopt[5]==sign) { \
        Ts *xval, *val = new Ts[fopt[0]*fopt[3]]; \
        cimg_forYZ(*this,y,z) { \
            cimg::fread(val,fopt[0]*fopt[3],nfile); \
            if (fopt[7]!=endian) cimg::invert_endianness(val,fopt[0]*fopt[3]); \
            xval = val; cimg_forX(*this,x) cimg_forV(*this,k) (*this)(x,y,z,k) = (T)*(xval++); \
          } \
        delete[] val; \
        loaded = true; \
      }

      if (!filename && !file)
        throw CImgArgumentException("CImg<%s>::load_inr() : Cannot load (null) filename.",
                                    pixel_type());
      cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"rb");
      int fopt[8], endian=cimg::endianness()?1:0;
      bool loaded = false;
      if (voxsize) voxsize[0]=voxsize[1]=voxsize[2]=1;
      _load_inr_header(nfile,fopt,voxsize);
      assign(fopt[0],fopt[1],fopt[2],fopt[3]);
      _cimg_load_inr_case(0,0,8, unsigned char);
      _cimg_load_inr_case(0,1,8, char);
      _cimg_load_inr_case(0,0,16,unsigned short);
      _cimg_load_inr_case(0,1,16,short);
      _cimg_load_inr_case(0,0,32,unsigned int);
      _cimg_load_inr_case(0,1,32,int);
      _cimg_load_inr_case(1,0,32,float);
      _cimg_load_inr_case(1,1,32,float);
      _cimg_load_inr_case(1,0,64,double);
      _cimg_load_inr_case(1,1,64,double);
      if (!loaded) {
        if (!file) cimg::fclose(nfile);
        throw CImgIOException("CImg<%s>::load_inr() : File '%s', cannot read images of the type specified in the file",
                              pixel_type(),filename?filename:"(FILE*)");
      }
      if (!file) cimg::fclose(nfile);
      return *this;
    }

    //! Load an image from a PANDORE file.
    CImg<T>& load_pandore(const char *const filename) {
      return _load_pandore(0,filename);
    }

    static CImg<T> get_load_pandore(const char *const filename) {
      return CImg<T>().load_pandore(filename);
    }

    //! Load an image from a PANDORE file.
    CImg<T>& load_pandore(cimg_std::FILE *const file) {
      return _load_pandore(file,0);
    }

    static CImg<T> get_load_pandore(cimg_std::FILE *const file) {
      return CImg<T>().load_pandore(file);
    }

    CImg<T>& _load_pandore(cimg_std::FILE *const file, const char *const filename) {
#define __cimg_load_pandore_case(nbdim,nwidth,nheight,ndepth,ndim,stype) \
        cimg::fread(dims,nbdim,nfile); \
        if (endian) cimg::invert_endianness(dims,nbdim); \
        assign(nwidth,nheight,ndepth,ndim); \
        const unsigned int siz = size(); \
        stype *buffer = new stype[siz]; \
        cimg::fread(buffer,siz,nfile); \
        if (endian) cimg::invert_endianness(buffer,siz); \
        T *ptrd = data; \
        cimg_foroff(*this,off) *(ptrd++) = (T)*(buffer++); \
        buffer-=siz; \
        delete[] buffer

#define _cimg_load_pandore_case(nbdim,nwidth,nheight,ndepth,dim,stype1,stype2,stype3,ltype) { \
        if (sizeof(stype1)==ltype) { __cimg_load_pandore_case(nbdim,nwidth,nheight,ndepth,dim,stype1); } \
        else if (sizeof(stype2)==ltype) { __cimg_load_pandore_case(nbdim,nwidth,nheight,ndepth,dim,stype2); } \
        else if (sizeof(stype3)==ltype) { __cimg_load_pandore_case(nbdim,nwidth,nheight,ndepth,dim,stype3); } \
        else throw CImgIOException("CImg<%s>::load_pandore() : File '%s' cannot be read, datatype not supported on this architecture.", \
                                   pixel_type(),filename?filename:"(FILE*)"); }

      if (!filename && !file)
        throw CImgArgumentException("CImg<%s>::load_pandore() : Cannot load (null) filename.",
                                    pixel_type());
      cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"rb");
      typedef unsigned char uchar;
      typedef unsigned short ushort;
      typedef unsigned int uint;
      typedef unsigned long ulong;
      char header[32];
      cimg::fread(header,12,nfile);
      if (cimg::strncasecmp("PANDORE",header,7)) {
        if (!file) cimg::fclose(nfile);
        throw CImgIOException("CImg<%s>::load_pandore() : File '%s' is not a valid PANDORE file, "
                              "(PANDORE identifier not found).",
                              pixel_type(),filename?filename:"(FILE*)");
      }
      unsigned int imageid, dims[8];
      cimg::fread(&imageid,1,nfile);
      const bool endian = (imageid>255);
      if (endian) cimg::invert_endianness(imageid);
      cimg::fread(header,20,nfile);

      switch (imageid) {
      case 2: _cimg_load_pandore_case(2,dims[1],1,1,1,uchar,uchar,uchar,1); break;
      case 3: _cimg_load_pandore_case(2,dims[1],1,1,1,long,int,short,4); break;
      case 4: _cimg_load_pandore_case(2,dims[1],1,1,1,double,float,float,4); break;
      case 5: _cimg_load_pandore_case(3,dims[2],dims[1],1,1,uchar,uchar,uchar,1); break;
      case 6: _cimg_load_pandore_case(3,dims[2],dims[1],1,1,long,int,short,4); break;
      case 7: _cimg_load_pandore_case(3,dims[2],dims[1],1,1,double,float,float,4); break;
      case 8: _cimg_load_pandore_case(4,dims[3],dims[2],dims[1],1,uchar,uchar,uchar,1); break;
      case 9: _cimg_load_pandore_case(4,dims[3],dims[2],dims[1],1,long,int,short,4); break;
      case 10: _cimg_load_pandore_case(4,dims[3],dims[2],dims[1],1,double,float,float,4); break;
      case 11 : { // Region 1D
        cimg::fread(dims,3,nfile);
        if (endian) cimg::invert_endianness(dims,3);
        assign(dims[1],1,1,1);
        const unsigned siz = size();
        if (dims[2]<256) {
          unsigned char *buffer = new unsigned char[siz];
          cimg::fread(buffer,siz,nfile);
          T *ptrd = data;
          cimg_foroff(*this,off) *(ptrd++) = (T)*(buffer++);
          buffer-=siz;
          delete[] buffer;
        } else {
          if (dims[2]<65536) {
            unsigned short *buffer = new unsigned short[siz];
            cimg::fread(buffer,siz,nfile);
            if (endian) cimg::invert_endianness(buffer,siz);
            T *ptrd = data;
            cimg_foroff(*this,off) *(ptrd++) = (T)*(buffer++);
            buffer-=siz;
            delete[] buffer;
          } else {
            unsigned int *buffer = new unsigned int[siz];
            cimg::fread(buffer,siz,nfile);
            if (endian) cimg::invert_endianness(buffer,siz);
            T *ptrd = data;
            cimg_foroff(*this,off) *(ptrd++) = (T)*(buffer++);
            buffer-=siz;
            delete[] buffer;
          }
        }
      }
        break;
      case 12 : { // Region 2D
        cimg::fread(dims,4,nfile);
        if (endian) cimg::invert_endianness(dims,4);
        assign(dims[2],dims[1],1,1);
        const unsigned int siz = size();
        if (dims[3]<256) {
          unsigned char *buffer = new unsigned char[siz];
          cimg::fread(buffer,siz,nfile);
          T *ptrd = data;
          cimg_foroff(*this,off) *(ptrd++) = (T)*(buffer++);
          buffer-=siz;
          delete[] buffer;
        } else {
          if (dims[3]<65536) {
            unsigned short *buffer = new unsigned short[siz];
            cimg::fread(buffer,siz,nfile);
            if (endian) cimg::invert_endianness(buffer,siz);
            T *ptrd = data;
            cimg_foroff(*this,off) *(ptrd++) = (T)*(buffer++);
            buffer-=siz;
            delete[] buffer;
          } else {
            unsigned long *buffer = new unsigned long[siz];
            cimg::fread(buffer,siz,nfile);
            if (endian) cimg::invert_endianness(buffer,siz);
            T *ptrd = data;
            cimg_foroff(*this,off) *(ptrd++) = (T)*(buffer++);
            buffer-=siz;
            delete[] buffer;
          }
        }
      }
        break;
      case 13 : { // Region 3D
        cimg::fread(dims,5,nfile);
        if (endian) cimg::invert_endianness(dims,5);
        assign(dims[3],dims[2],dims[1],1);
        const unsigned int siz = size();
        if (dims[4]<256) {
          unsigned char *buffer = new unsigned char[siz];
          cimg::fread(buffer,siz,nfile);
          T *ptrd = data;
          cimg_foroff(*this,off) *(ptrd++) = (T)*(buffer++);
          buffer-=siz;
          delete[] buffer;
        } else {
          if (dims[4]<65536) {
            unsigned short *buffer = new unsigned short[siz];
            cimg::fread(buffer,siz,nfile);
            if (endian) cimg::invert_endianness(buffer,siz);
            T *ptrd = data;
            cimg_foroff(*this,off) *(ptrd++) = (T)*(buffer++);
            buffer-=siz;
            delete[] buffer;
          } else {
            unsigned int *buffer = new unsigned int[siz];
            cimg::fread(buffer,siz,nfile);
            if (endian) cimg::invert_endianness(buffer,siz);
            T *ptrd = data;
            cimg_foroff(*this,off) *(ptrd++) = (T)*(buffer++);
            buffer-=siz;
            delete[] buffer;
          }
        }
      }
        break;
      case 16: _cimg_load_pandore_case(4,dims[2],dims[1],1,3,uchar,uchar,uchar,1); break;
      case 17: _cimg_load_pandore_case(4,dims[2],dims[1],1,3,long,int,short,4); break;
      case 18: _cimg_load_pandore_case(4,dims[2],dims[1],1,3,double,float,float,4); break;
      case 19: _cimg_load_pandore_case(5,dims[3],dims[2],dims[1],3,uchar,uchar,uchar,1); break;
      case 20: _cimg_load_pandore_case(5,dims[3],dims[2],dims[1],3,long,int,short,4); break;
      case 21: _cimg_load_pandore_case(5,dims[3],dims[2],dims[1],3,double,float,float,4); break;
      case 22: _cimg_load_pandore_case(2,dims[1],1,1,dims[0],uchar,uchar,uchar,1); break;
      case 23: _cimg_load_pandore_case(2,dims[1],1,1,dims[0],long,int,short,4);
      case 24: _cimg_load_pandore_case(2,dims[1],1,1,dims[0],ulong,uint,ushort,4); break;
      case 25: _cimg_load_pandore_case(2,dims[1],1,1,dims[0],double,float,float,4); break;
      case 26: _cimg_load_pandore_case(3,dims[2],dims[1],1,dims[0],uchar,uchar,uchar,1); break;
      case 27: _cimg_load_pandore_case(3,dims[2],dims[1],1,dims[0],long,int,short,4); break;
      case 28: _cimg_load_pandore_case(3,dims[2],dims[1],1,dims[0],ulong,uint,ushort,4); break;
      case 29: _cimg_load_pandore_case(3,dims[2],dims[1],1,dims[0],double,float,float,4); break;
      case 30: _cimg_load_pandore_case(4,dims[3],dims[2],dims[1],dims[0],uchar,uchar,uchar,1); break;
      case 31: _cimg_load_pandore_case(4,dims[3],dims[2],dims[1],dims[0],long,int,short,4); break;
      case 32: _cimg_load_pandore_case(4,dims[3],dims[2],dims[1],dims[0],ulong,uint,ushort,4); break;
      case 33: _cimg_load_pandore_case(4,dims[3],dims[2],dims[1],dims[0],double,float,float,4); break;
      case 34 : { // Points 1D
        int ptbuf[4];
        cimg::fread(ptbuf,1,nfile);
        if (endian) cimg::invert_endianness(ptbuf,1);
        assign(1); (*this)(0) = (T)ptbuf[0];
      } break;
      case 35 : { // Points 2D
        int ptbuf[4];
        cimg::fread(ptbuf,2,nfile);
        if (endian) cimg::invert_endianness(ptbuf,2);
        assign(2); (*this)(0) = (T)ptbuf[1]; (*this)(1) = (T)ptbuf[0];
      } break;
      case 36 : { // Points 3D
        int ptbuf[4];
        cimg::fread(ptbuf,3,nfile);
        if (endian) cimg::invert_endianness(ptbuf,3);
        assign(3); (*this)(0) = (T)ptbuf[2]; (*this)(1) = (T)ptbuf[1]; (*this)(2) = (T)ptbuf[0];
      } break;
      default :
        if (!file) cimg::fclose(nfile);
        throw CImgIOException("CImg<%s>::load_pandore() : File '%s', cannot read images with ID_type = %u",
                              pixel_type(),filename?filename:"(FILE*)",imageid);
      }
      if (!file) cimg::fclose(nfile);
      return *this;
    }

    //! Load an image from a PAR-REC (Philips) file.
    CImg<T>& load_parrec(const char *const filename, const char axis='v', const char align='p') {
      CImgList<T> list;
      list.load_parrec(filename);
      if (list.size==1) return list[0].transfer_to(*this);
      return assign(list.get_append(axis,align));
    }

    static CImg<T> get_load_parrec(const char *const filename, const char axis='v', const char align='p') {
      return CImg<T>().load_parrec(filename,axis,align);
    }

    //! Load an image from a .RAW file.
    CImg<T>& load_raw(const char *const filename,
                      const unsigned int sizex, const unsigned int sizey=1,
                      const unsigned int sizez=1, const unsigned int sizev=1,
                      const bool multiplexed=false, const bool invert_endianness=false) {
      return _load_raw(0,filename,sizex,sizey,sizez,sizev,multiplexed,invert_endianness);
    }

    static CImg<T> get_load_raw(const char *const filename,
                                const unsigned int sizex, const unsigned int sizey=1,
                                const unsigned int sizez=1, const unsigned int sizev=1,
                                const bool multiplexed=false, const bool invert_endianness=false) {
      return CImg<T>().load_raw(filename,sizex,sizey,sizez,sizev,multiplexed,invert_endianness);
    }

    //! Load an image from a .RAW file.
    CImg<T>& load_raw(cimg_std::FILE *const file,
                      const unsigned int sizex, const unsigned int sizey=1,
                      const unsigned int sizez=1, const unsigned int sizev=1,
                      const bool multiplexed=false, const bool invert_endianness=false) {
      return _load_raw(file,0,sizex,sizey,sizez,sizev,multiplexed,invert_endianness);
    }

    static CImg<T> get_load_raw(cimg_std::FILE *const file,
                                const unsigned int sizex, const unsigned int sizey=1,
                                const unsigned int sizez=1, const unsigned int sizev=1,
                                const bool multiplexed=false, const bool invert_endianness=false) {
      return CImg<T>().load_raw(file,sizex,sizey,sizez,sizev,multiplexed,invert_endianness);
    }

    CImg<T>& _load_raw(cimg_std::FILE *const file, const char *const filename,
                       const unsigned int sizex, const unsigned int sizey,
                       const unsigned int sizez, const unsigned int sizev,
                       const bool multiplexed, const bool invert_endianness) {
      if (!filename && !file)
        throw CImgArgumentException("CImg<%s>::load_raw() : Cannot load (null) filename.",
                                    pixel_type());
      assign(sizex,sizey,sizez,sizev,0);
      const unsigned int siz = size();
      if (siz) {
        cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"rb");
        if (!multiplexed) {
          cimg::fread(data,siz,nfile);
          if (invert_endianness) cimg::invert_endianness(data,siz);
        }
        else {
          CImg<T> buf(1,1,1,sizev);
          cimg_forXYZ(*this,x,y,z) {
            cimg::fread(buf.data,sizev,nfile);
            if (invert_endianness) cimg::invert_endianness(buf.data,sizev);
            set_vector_at(buf,x,y,z); }
        }
        if (!file) cimg::fclose(nfile);
      }
      return *this;
    }

    //! Load a video sequence using FFMPEG av's libraries.
    CImg<T>& load_ffmpeg(const char *const filename, const unsigned int first_frame=0, const unsigned int last_frame=~0U,
                         const unsigned int step_frame=1, const bool pixel_format=true, const bool resume=false,
                         const char axis='z', const char align='p') {
      return get_load_ffmpeg(filename,first_frame,last_frame,step_frame,pixel_format,resume,axis,align).transfer_to(*this);
    }

    static CImg<T> get_load_ffmpeg(const char *const filename, const unsigned int first_frame=0, const unsigned int last_frame=~0U,
                                   const unsigned int step_frame=1, const bool pixel_format=true, const bool resume=false,
                                   const char axis='z', const char align='p') {
      return CImgList<T>().load_ffmpeg(filename,first_frame,last_frame,step_frame,pixel_format,resume).get_append(axis,align);
    }

    //! Load an image sequence from a YUV file.
    CImg<T>& load_yuv(const char *const filename,
                      const unsigned int sizex, const unsigned int sizey=1,
                      const unsigned int first_frame=0, const unsigned int last_frame=~0U,
                      const unsigned int step_frame=1, const bool yuv2rgb=true, const char axis='z', const char align='p') {
      return get_load_yuv(filename,sizex,sizey,first_frame,last_frame,step_frame,yuv2rgb,axis,align).transfer_to(*this);
    }

    static CImg<T> get_load_yuv(const char *const filename,
                                const unsigned int sizex, const unsigned int sizey=1,
                                const unsigned int first_frame=0, const unsigned int last_frame=~0U,
                                const unsigned int step_frame=1, const bool yuv2rgb=true, const char axis='z', const char align='p') {
      return CImgList<T>().load_yuv(filename,sizex,sizey,first_frame,last_frame,step_frame,yuv2rgb).get_append(axis,align);
    }

    //! Load an image sequence from a YUV file.
    CImg<T>& load_yuv(cimg_std::FILE *const file,
                      const unsigned int sizex, const unsigned int sizey=1,
                      const unsigned int first_frame=0, const unsigned int last_frame=~0U,
                      const unsigned int step_frame=1, const bool yuv2rgb=true, const char axis='z', const char align='p') {
      return get_load_yuv(file,sizex,sizey,first_frame,last_frame,step_frame,yuv2rgb,axis,align).transfer_to(*this);
    }

    static CImg<T> get_load_yuv(cimg_std::FILE *const file,
                                const unsigned int sizex, const unsigned int sizey=1,
                                const unsigned int first_frame=0, const unsigned int last_frame=~0U,
                                const unsigned int step_frame=1, const bool yuv2rgb=true, const char axis='z', const char align='p') {
      return CImgList<T>().load_yuv(file,sizex,sizey,first_frame,last_frame,step_frame,yuv2rgb).get_append(axis,align);
    }

    //! Load a 3D object from a .OFF file.
    template<typename tf, typename tc>
    CImg<T>& load_off(const char *const filename, CImgList<tf>& primitives, CImgList<tc>& colors, const bool invert_faces=false) {
      return _load_off(0,filename,primitives,colors,invert_faces);
    }

    template<typename tf, typename tc>
    static CImg<T> get_load_off(const char *const filename, CImgList<tf>& primitives, CImgList<tc>& colors,
                                const bool invert_faces=false) {
      return CImg<T>().load_off(filename,primitives,colors,invert_faces);
    }

    //! Load a 3D object from a .OFF file.
    template<typename tf, typename tc>
    CImg<T>& load_off(cimg_std::FILE *const file, CImgList<tf>& primitives, CImgList<tc>& colors, const bool invert_faces=false) {
      return _load_off(file,0,primitives,colors,invert_faces);
    }

    template<typename tf, typename tc>
    static CImg<T> get_load_off(cimg_std::FILE *const file, CImgList<tf>& primitives, CImgList<tc>& colors,
                                const bool invert_faces=false) {
      return CImg<T>().load_off(file,primitives,colors,invert_faces);
    }

    template<typename tf, typename tc>
    CImg<T>& _load_off(cimg_std::FILE *const file, const char *const filename,
                       CImgList<tf>& primitives, CImgList<tc>& colors, const bool invert_faces) {
      if (!filename && !file)
        throw CImgArgumentException("CImg<%s>::load_off() : Cannot load (null) filename.",
                                    pixel_type());
      cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"r");
      unsigned int nb_points = 0, nb_primitives = 0, nb_read = 0;
      char line[256] = { 0 };
      int err;

      // Skip comments, and read magic string OFF
      do { err = cimg_std::fscanf(nfile,"%255[^\n] ",line); } while (!err || (err==1 && line[0]=='#'));
      if (cimg::strncasecmp(line,"OFF",3) && cimg::strncasecmp(line,"COFF",4)) {
        if (!file) cimg::fclose(nfile);
        throw CImgIOException("CImg<%s>::load_off() : File '%s', keyword 'OFF' not found.",
                              pixel_type(),filename?filename:"(FILE*)");
      }
      do { err = cimg_std::fscanf(nfile,"%255[^\n] ",line); } while (!err || (err==1 && line[0]=='#'));
      if ((err = cimg_std::sscanf(line,"%u%u%*[^\n] ",&nb_points,&nb_primitives))!=2) {
        if (!file) cimg::fclose(nfile);
        throw CImgIOException("CImg<%s>::load_off() : File '%s', invalid vertices/primitives numbers.",
                              pixel_type(),filename?filename:"(FILE*)");
      }

      // Read points data
      assign(nb_points,3);
      float X = 0, Y = 0, Z = 0;
      cimg_forX(*this,l) {
        do { err = cimg_std::fscanf(nfile,"%255[^\n] ",line); } while (!err || (err==1 && line[0]=='#'));
        if ((err = cimg_std::sscanf(line,"%f%f%f%*[^\n] ",&X,&Y,&Z))!=3) {
          if (!file) cimg::fclose(nfile);
          throw CImgIOException("CImg<%s>::load_off() : File '%s', cannot read point %u/%u.\n",
                                pixel_type(),filename?filename:"(FILE*)",l+1,nb_points);
        }
        (*this)(l,0) = (T)X; (*this)(l,1) = (T)Y; (*this)(l,2) = (T)Z;
      }

      // Read primitive data
      primitives.assign();
      colors.assign();
      bool stopflag = false;
      while (!stopflag) {
        float c0 = 0.7f, c1 = 0.7f, c2 = 0.7f;
        unsigned int prim = 0, i0 = 0, i1 = 0, i2 = 0, i3 = 0, i4 = 0, i5 = 0, i6 = 0, i7 = 0;
        line[0]='\0';
        if ((err = cimg_std::fscanf(nfile,"%u",&prim))!=1) stopflag=true;
        else {
          ++nb_read;
          switch (prim) {
          case 1 : {
            if ((err = cimg_std::fscanf(nfile,"%u%255[^\n] ",&i0,line))<2) {
              cimg::warn("CImg<%s>::load_off() : File '%s', invalid primitive %u/%u.",
                         pixel_type(),filename?filename:"(FILE*)",nb_read,nb_primitives);
              err = cimg_std::fscanf(nfile,"%*[^\n] ");
            } else {
              err = cimg_std::sscanf(line,"%f%f%f",&c0,&c1,&c2);
              primitives.insert(CImg<tf>::vector(i0));
              colors.insert(CImg<tc>::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255)));
            }
          } break;
          case 2 : {
            if ((err = cimg_std::fscanf(nfile,"%u%u%255[^\n] ",&i0,&i1,line))<2) {
              cimg::warn("CImg<%s>::load_off() : File '%s', invalid primitive %u/%u.",
                         pixel_type(),filename?filename:"(FILE*)",nb_read,nb_primitives);
              err = cimg_std::fscanf(nfile,"%*[^\n] ");
            } else {
              err = cimg_std::sscanf(line,"%f%f%f",&c0,&c1,&c2);
              primitives.insert(CImg<tf>::vector(i0,i1));
              colors.insert(CImg<tc>::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255)));
            }
          } break;
          case 3 : {
            if ((err = cimg_std::fscanf(nfile,"%u%u%u%255[^\n] ",&i0,&i1,&i2,line))<3) {
              cimg::warn("CImg<%s>::load_off() : File '%s', invalid primitive %u/%u.",
                         pixel_type(),filename?filename:"(FILE*)",nb_read,nb_primitives);
              err = cimg_std::fscanf(nfile,"%*[^\n] ");
            } else {
              err = cimg_std::sscanf(line,"%f%f%f",&c0,&c1,&c2);
              if (invert_faces) primitives.insert(CImg<tf>::vector(i0,i1,i2));
              else primitives.insert(CImg<tf>::vector(i0,i2,i1));
              colors.insert(CImg<tc>::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255)));
            }
          } break;
          case 4 : {
            if ((err = cimg_std::fscanf(nfile,"%u%u%u%u%255[^\n] ",&i0,&i1,&i2,&i3,line))<4) {
              cimg::warn("CImg<%s>::load_off() : File '%s', invalid primitive %u/%u.",
                         pixel_type(),filename?filename:"(FILE*)",nb_read,nb_primitives);
              err = cimg_std::fscanf(nfile,"%*[^\n] ");
            } else {
              err = cimg_std::sscanf(line,"%f%f%f",&c0,&c1,&c2);
              if (invert_faces) primitives.insert(CImg<tf>::vector(i0,i1,i2,i3));
              else primitives.insert(CImg<tf>::vector(i0,i3,i2,i1));
              colors.insert(CImg<tc>::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255),(tc)(c2*255)));
            }
          } break;
          case 5 : {
            if ((err = cimg_std::fscanf(nfile,"%u%u%u%u%u%255[^\n] ",&i0,&i1,&i2,&i3,&i4,line))<5) {
              cimg::warn("CImg<%s>::load_off() : File '%s', invalid primitive %u/%u.",
                         pixel_type(),filename?filename:"(FILE*)",nb_read,nb_primitives);
              err = cimg_std::fscanf(nfile,"%*[^\n] ");
            } else {
              err = cimg_std::sscanf(line,"%f%f%f",&c0,&c1,&c2);
              if (invert_faces) {
                primitives.insert(CImg<tf>::vector(i0,i1,i2,i3));
                primitives.insert(CImg<tf>::vector(i0,i3,i4));
              }
              else {
                primitives.insert(CImg<tf>::vector(i0,i3,i2,i1));
                primitives.insert(CImg<tf>::vector(i0,i4,i3));
              }
              colors.insert(2,CImg<tc>::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255),(tc)(c2*255)));
              ++nb_primitives;
            }
          } break;
          case 6 : {
            if ((err = cimg_std::fscanf(nfile,"%u%u%u%u%u%u%255[^\n] ",&i0,&i1,&i2,&i3,&i4,&i5,line))<6) {
              cimg::warn("CImg<%s>::load_off() : File '%s', invalid primitive %u/%u.",
                         pixel_type(),filename?filename:"(FILE*)",nb_read,nb_primitives);
              err = cimg_std::fscanf(nfile,"%*[^\n] ");
            } else {
              err = cimg_std::sscanf(line,"%f%f%f",&c0,&c1,&c2);
              if (invert_faces) {
                primitives.insert(CImg<tf>::vector(i0,i1,i2,i3));
                primitives.insert(CImg<tf>::vector(i0,i3,i4,i5));
              }
              else {
                primitives.insert(CImg<tf>::vector(i0,i3,i2,i1));
                primitives.insert(CImg<tf>::vector(i0,i5,i4,i3));
              }
              colors.insert(2,CImg<tc>::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255),(tc)(c2*255)));
              ++nb_primitives;
            }
          } break;
          case 7 : {
            if ((err = cimg_std::fscanf(nfile,"%u%u%u%u%u%u%u%255[^\n] ",&i0,&i1,&i2,&i3,&i4,&i5,&i6,line))<7) {
              cimg::warn("CImg<%s>::load_off() : File '%s', invalid primitive %u/%u.",
                         pixel_type(),filename?filename:"(FILE*)",nb_read,nb_primitives);
              err = cimg_std::fscanf(nfile,"%*[^\n] ");
            } else {
              err = cimg_std::sscanf(line,"%f%f%f",&c0,&c1,&c2);
              if (invert_faces) {
                primitives.insert(CImg<tf>::vector(i0,i1,i3,i4));
                primitives.insert(CImg<tf>::vector(i0,i4,i5,i6));
                primitives.insert(CImg<tf>::vector(i1,i2,i3));
              }
              else {
                primitives.insert(CImg<tf>::vector(i0,i4,i3,i1));
                primitives.insert(CImg<tf>::vector(i0,i6,i5,i4));
                primitives.insert(CImg<tf>::vector(i3,i2,i1));
              }
              colors.insert(2,CImg<tc>::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255),(tc)(c2*255)));
              ++(++nb_primitives);
            }
          } break;
          case 8 : {
            if ((err = cimg_std::fscanf(nfile,"%u%u%u%u%u%u%u%u%255[^\n] ",&i0,&i1,&i2,&i3,&i4,&i5,&i6,&i7,line))<7) {
              cimg::warn("CImg<%s>::load_off() : File '%s', invalid primitive %u/%u.",
                         pixel_type(),filename?filename:"(FILE*)",nb_read,nb_primitives);
              err = cimg_std::fscanf(nfile,"%*[^\n] ");
            } else {
              err = cimg_std::sscanf(line,"%f%f%f",&c0,&c1,&c2);
              if (invert_faces) {
                primitives.insert(CImg<tf>::vector(i0,i1,i2,i3));
                primitives.insert(CImg<tf>::vector(i0,i3,i4,i5));
                primitives.insert(CImg<tf>::vector(i0,i5,i6,i7));
              }
              else {
                primitives.insert(CImg<tf>::vector(i0,i3,i2,i1));
                primitives.insert(CImg<tf>::vector(i0,i5,i4,i3));
                primitives.insert(CImg<tf>::vector(i0,i7,i6,i5));
              }
              colors.insert(2,CImg<tc>::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255),(tc)(c2*255)));
              ++(++nb_primitives);
            }
          } break;
          default :
            cimg::warn("CImg<%s>::load_off() : File '%s', invalid primitive %u/%u (%u vertices).",
                       pixel_type(),filename?filename:"(FILE*)",nb_read,nb_primitives,prim);
            err = cimg_std::fscanf(nfile,"%*[^\n] ");
          }
        }
      }
      if (!file) cimg::fclose(nfile);
      if (primitives.size!=nb_primitives)
        cimg::warn("CImg<%s>::load_off() : File '%s', read only %u primitives instead of %u as claimed in the header.",
                   pixel_type(),filename?filename:"(FILE*)",primitives.size,nb_primitives);
      return *this;
    }

    //! Load a video sequence using FFMPEG's external tool 'ffmpeg'.
    CImg<T>& load_ffmpeg_external(const char *const filename, const char axis='z', const char align='p') {
      return get_load_ffmpeg_external(filename,axis,align).transfer_to(*this);
    }

    static CImg<T> get_load_ffmpeg_external(const char *const filename, const char axis='z', const char align='p') {
      return CImgList<T>().load_ffmpeg_external(filename).get_append(axis,align);
    }

    //! Load an image using GraphicsMagick's external tool 'gm'.
    CImg<T>& load_graphicsmagick_external(const char *const filename) {
      if (!filename)
        throw CImgArgumentException("CImg<%s>::load_graphicsmagick_external() : Cannot load (null) filename.",
                                    pixel_type());
      char command[1024], filetmp[512];
      cimg_std::FILE *file = 0;
#if cimg_OS==1
      cimg_std::sprintf(command,"%s convert \"%s\" ppm:-",cimg::graphicsmagick_path(),filename);
      file = popen(command,"r");
      if (file) { load_pnm(file); pclose(file); return *this; }
#endif
      do {
        cimg_std::sprintf(filetmp,"%s%s%s.ppm",cimg::temporary_path(),cimg_OS==2?"\\":"/",cimg::filenamerand());
        if ((file=cimg_std::fopen(filetmp,"rb"))!=0) cimg_std::fclose(file);
      } while (file);
      cimg_std::sprintf(command,"%s convert \"%s\" %s",cimg::graphicsmagick_path(),filename,filetmp);
      cimg::system(command,cimg::graphicsmagick_path());
      if (!(file = cimg_std::fopen(filetmp,"rb"))) {
        cimg::fclose(cimg::fopen(filename,"r"));
        throw CImgIOException("CImg<%s>::load_graphicsmagick_external() : Failed to open image '%s'.\n\n"
                              "Path of 'GraphicsMagick's gm' : \"%s\"\n"
                              "Path of temporary filename : \"%s\"",
                              pixel_type(),filename,cimg::graphicsmagick_path(),filetmp);
      } else cimg::fclose(file);
      load_pnm(filetmp);
      cimg_std::remove(filetmp);
      return *this;
    }

    static CImg<T> get_load_graphicsmagick_external(const char *const filename) {
      return CImg<T>().load_graphicsmagick_external(filename);
    }

    //! Load a gzipped image file, using external tool 'gunzip'.
    CImg<T>& load_gzip_external(const char *const filename) {
      if (!filename)
        throw CImgIOException("CImg<%s>::load_gzip_external() : Cannot load (null) filename.",
                              pixel_type());
      char command[1024], filetmp[512], body[512];
      const char
        *ext = cimg::split_filename(filename,body),
        *ext2 = cimg::split_filename(body,0);
      cimg_std::FILE *file = 0;
      do {
        if (!cimg::strcasecmp(ext,"gz")) {
          if (*ext2) cimg_std::sprintf(filetmp,"%s%s%s.%s",cimg::temporary_path(),cimg_OS==2?"\\":"/",
                                  cimg::filenamerand(),ext2);
          else cimg_std::sprintf(filetmp,"%s%s%s",cimg::temporary_path(),cimg_OS==2?"\\":"/",
                                  cimg::filenamerand());
        } else {
           if (*ext) cimg_std::sprintf(filetmp,"%s%s%s.%s",cimg::temporary_path(),cimg_OS==2?"\\":"/",
                                  cimg::filenamerand(),ext);
           else cimg_std::sprintf(filetmp,"%s%s%s",cimg::temporary_path(),cimg_OS==2?"\\":"/",
                             cimg::filenamerand());
        }
        if ((file=cimg_std::fopen(filetmp,"rb"))!=0) cimg_std::fclose(file);
      } while (file);
      cimg_std::sprintf(command,"%s -c \"%s\" > %s",cimg::gunzip_path(),filename,filetmp);
      cimg::system(command);
      if (!(file = cimg_std::fopen(filetmp,"rb"))) {
        cimg::fclose(cimg::fopen(filename,"r"));
        throw CImgIOException("CImg<%s>::load_gzip_external() : File '%s' cannot be opened.",
                              pixel_type(),filename);
      } else cimg::fclose(file);
      load(filetmp);
      cimg_std::remove(filetmp);
      return *this;
    }

    static CImg<T> get_load_gzip_external(const char *const filename) {
      return CImg<T>().load_gzip_external(filename);
    }

    //! Load an image using ImageMagick's external tool 'convert'.
    CImg<T>& load_imagemagick_external(const char *const filename) {
      if (!filename)
        throw CImgArgumentException("CImg<%s>::load_imagemagick_external() : Cannot load (null) filename.",
                                    pixel_type());
      char command[1024], filetmp[512];
      cimg_std::FILE *file = 0;
#if cimg_OS==1
      cimg_std::sprintf(command,"%s \"%s\" ppm:-",cimg::imagemagick_path(),filename);
      file = popen(command,"r");
      if (file) { load_pnm(file); pclose(file); return *this; }
#endif
      do {
        cimg_std::sprintf(filetmp,"%s%s%s.ppm",cimg::temporary_path(),cimg_OS==2?"\\":"/",cimg::filenamerand());
        if ((file=cimg_std::fopen(filetmp,"rb"))!=0) cimg_std::fclose(file);
      } while (file);
      cimg_std::sprintf(command,"%s \"%s\" %s",cimg::imagemagick_path(),filename,filetmp);
      cimg::system(command,cimg::imagemagick_path());
      if (!(file = cimg_std::fopen(filetmp,"rb"))) {
        cimg::fclose(cimg::fopen(filename,"r"));
        throw CImgIOException("CImg<%s>::load_imagemagick_external() : Failed to open image '%s'.\n\n"
                              "Path of 'ImageMagick's convert' : \"%s\"\n"
                              "Path of temporary filename : \"%s\"",
                              pixel_type(),filename,cimg::imagemagick_path(),filetmp);
      } else cimg::fclose(file);
      load_pnm(filetmp);
      cimg_std::remove(filetmp);
      return *this;
    }

    static CImg<T> get_load_imagemagick_external(const char *const filename) {
      return CImg<T>().load_imagemagick_external(filename);
    }

    //! Load a DICOM image file, using XMedcon's external tool 'medcon'.
    CImg<T>& load_medcon_external(const char *const filename) {
      if (!filename)
        throw CImgArgumentException("CImg<%s>::load_medcon_external() : Cannot load (null) filename.",
                                    pixel_type());
      char command[1024], filetmp[512], body[512];
      cimg::fclose(cimg::fopen(filename,"r"));
      cimg_std::FILE *file = 0;
      do {
        cimg_std::sprintf(filetmp,"%s.hdr",cimg::filenamerand());
        if ((file=cimg_std::fopen(filetmp,"rb"))!=0) cimg_std::fclose(file);
      } while (file);
      cimg_std::sprintf(command,"%s -w -c anlz -o %s -f %s",cimg::medcon_path(),filetmp,filename);
      cimg::system(command);
      cimg::split_filename(filetmp,body);
      cimg_std::sprintf(command,"m000-%s.hdr",body);
      file = cimg_std::fopen(command,"rb");
      if (!file) {
        throw CImgIOException("CImg<%s>::load_medcon_external() : Failed to open image '%s'.\n\n"
                              "Path of 'medcon' : \"%s\"\n"
                              "Path of temporary filename : \"%s\"",
                              pixel_type(),filename,cimg::medcon_path(),filetmp);
      } else cimg::fclose(file);
      load_analyze(command);
      cimg_std::remove(command);
      cimg_std::sprintf(command,"m000-%s.img",body);
      cimg_std::remove(command);
      return *this;
    }

    static CImg<T> get_load_medcon_external(const char *const filename) {
      return CImg<T>().load_medcon_external(filename);
    }

    //! Load a RAW Color Camera image file, using external tool 'dcraw'.
    CImg<T>& load_dcraw_external(const char *const filename) {
      if (!filename)
        throw CImgArgumentException("CImg<%s>::load_dcraw_external() : Cannot load (null) filename.",
                                    pixel_type());
      char command[1024], filetmp[512];
      cimg_std::FILE *file = 0;
#if cimg_OS==1
      cimg_std::sprintf(command,"%s -4 -c \"%s\"",cimg::dcraw_path(),filename);
      file = popen(command,"r");
      if (file) { load_pnm(file); pclose(file); return *this; }
#endif
      do {
        cimg_std::sprintf(filetmp,"%s%s%s.ppm",cimg::temporary_path(),cimg_OS==2?"\\":"/",cimg::filenamerand());
        if ((file=cimg_std::fopen(filetmp,"rb"))!=0) cimg_std::fclose(file);
      } while (file);
      cimg_std::sprintf(command,"%s -4 -c \"%s\" > %s",cimg::dcraw_path(),filename,filetmp);
      cimg::system(command,cimg::dcraw_path());
      if (!(file = cimg_std::fopen(filetmp,"rb"))) {
        cimg::fclose(cimg::fopen(filename,"r"));
        throw CImgIOException("CImg<%s>::load_dcraw_external() : Failed to open image '%s'.\n\n"
                              "Path of 'dcraw' : \"%s\"\n"
                              "Path of temporary filename : \"%s\"",
                              pixel_type(),filename,cimg::dcraw_path(),filetmp);
      } else cimg::fclose(file);
      load_pnm(filetmp);
      cimg_std::remove(filetmp);
      return *this;
    }

    static CImg<T> get_load_dcraw_external(const char *const filename) {
      return CImg<T>().load_dcraw_external(filename);
    }

    //! Load an image using ImageMagick's or GraphicsMagick's executables.
    CImg<T>& load_other(const char *const filename) {
      if (!filename)
        throw CImgArgumentException("CImg<%s>::load_other() : Cannot load (null) filename.",
                                    pixel_type());
      const unsigned int odebug = cimg::exception_mode();
      cimg::exception_mode() = 0;
      try { load_magick(filename); }
      catch (CImgException&) {
        try { load_imagemagick_external(filename); }
        catch (CImgException&) {
          try { load_graphicsmagick_external(filename); }
          catch (CImgException&) {
            assign();
          }
        }
      }
      cimg::exception_mode() = odebug;
      if (is_empty())
        throw CImgIOException("CImg<%s>::load_other() : File '%s' cannot be opened.",
                              pixel_type(),filename);
      return *this;
    }

    static CImg<T> get_load_other(const char *const filename) {
      return CImg<T>().load_other(filename);
    }

    //@}
    //---------------------------
    //
    //! \name Image File Saving
    //@{
    //---------------------------

    //! Save the image as a file.
    /**
       The used file format is defined by the file extension in the filename \p filename.
       Parameter \p number can be used to add a 6-digit number to the filename before saving.
    **/
    const CImg<T>& save(const char *const filename, const int number=-1) const {
      if (is_empty())
        throw CImgInstanceException("CImg<%s>::save() : File '%s', instance image (%u,%u,%u,%u,%p) is empty.",
                                    pixel_type(),filename?filename:"(null)",width,height,depth,dim,data);
      if (!filename)
        throw CImgArgumentException("CImg<%s>::save() : Instance image (%u,%u,%u,%u,%p) cannot be saved as a (null) filename.",
                                    pixel_type(),width,height,depth,dim,data);
      const char *ext = cimg::split_filename(filename);
      char nfilename[1024];
      const char *const fn = (number>=0)?cimg::number_filename(filename,number,6,nfilename):filename;
#ifdef cimg_save_plugin
      cimg_save_plugin(fn);
#endif
#ifdef cimg_save_plugin1
      cimg_save_plugin1(fn);
#endif
#ifdef cimg_save_plugin2
      cimg_save_plugin2(fn);
#endif
#ifdef cimg_save_plugin3
      cimg_save_plugin3(fn);
#endif
#ifdef cimg_save_plugin4
      cimg_save_plugin4(fn);
#endif
#ifdef cimg_save_plugin5
      cimg_save_plugin5(fn);
#endif
#ifdef cimg_save_plugin6
      cimg_save_plugin6(fn);
#endif
#ifdef cimg_save_plugin7
      cimg_save_plugin7(fn);
#endif
#ifdef cimg_save_plugin8
      cimg_save_plugin8(fn);
#endif
      // ASCII formats
      if (!cimg::strcasecmp(ext,"asc")) return save_ascii(fn);
      if (!cimg::strcasecmp(ext,"dlm") ||
          !cimg::strcasecmp(ext,"txt")) return save_dlm(fn);
      if (!cimg::strcasecmp(ext,"cpp") ||
          !cimg::strcasecmp(ext,"hpp") ||
          !cimg::strcasecmp(ext,"h") ||
          !cimg::strcasecmp(ext,"c")) return save_cpp(fn);

      // 2D binary formats
      if (!cimg::strcasecmp(ext,"bmp")) return save_bmp(fn);
      if (!cimg::strcasecmp(ext,"jpg") ||
          !cimg::strcasecmp(ext,"jpeg") ||
          !cimg::strcasecmp(ext,"jpe") ||
          !cimg::strcasecmp(ext,"jfif") ||
          !cimg::strcasecmp(ext,"jif")) return save_jpeg(fn);
      if (!cimg::strcasecmp(ext,"rgb")) return save_rgb(fn);
      if (!cimg::strcasecmp(ext,"rgba")) return save_rgba(fn);
      if (!cimg::strcasecmp(ext,"png")) return save_png(fn);
      if (!cimg::strcasecmp(ext,"pgm") ||
          !cimg::strcasecmp(ext,"ppm") ||
          !cimg::strcasecmp(ext,"pnm")) return save_pnm(fn);
      if (!cimg::strcasecmp(ext,"tif") ||
          !cimg::strcasecmp(ext,"tiff")) return save_tiff(fn);

      // 3D binary formats
      if (!cimg::strcasecmp(ext,"cimgz")) return save_cimg(fn,true);
      if (!cimg::strcasecmp(ext,"cimg") || ext[0]=='\0') return save_cimg(fn,false);
      if (!cimg::strcasecmp(ext,"dcm")) return save_medcon_external(fn);
      if (!cimg::strcasecmp(ext,"hdr") ||
          !cimg::strcasecmp(ext,"nii")) return save_analyze(fn);
      if (!cimg::strcasecmp(ext,"inr")) return save_inr(fn);
      if (!cimg::strcasecmp(ext,"pan")) return save_pandore(fn);
      if (!cimg::strcasecmp(ext,"raw")) return save_raw(fn);

      // Archive files
      if (!cimg::strcasecmp(ext,"gz")) return save_gzip_external(fn);

      // Image sequences
      if (!cimg::strcasecmp(ext,"yuv")) return save_yuv(fn,true);
      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,"mkv") ||
          !cimg::strcasecmp(ext,"mpe") ||
          !cimg::strcasecmp(ext,"movie") ||
          !cimg::strcasecmp(ext,"ogm") ||
          !cimg::strcasecmp(ext,"qt") ||
          !cimg::strcasecmp(ext,"rm") ||
          !cimg::strcasecmp(ext,"vob") ||
          !cimg::strcasecmp(ext,"wmv") ||
          !cimg::strcasecmp(ext,"xvid") ||
          !cimg::strcasecmp(ext,"mpeg")) return save_ffmpeg(fn);
      return save_other(fn);
    }

    // Save the image as an ASCII file (ASCII Raw + simple header) (internal).
    const CImg<T>& _save_ascii(cimg_std::FILE *const file, const char *const filename) const {
      if (is_empty())
        throw CImgInstanceException("CImg<%s>::save_ascii() : File '%s', instance image (%u,%u,%u,%u,%p) is empty.",
                                    pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data);
      if (!file && !filename)
        throw CImgArgumentException("CImg<%s>::save_ascii() : Instance image (%u,%u,%u,%u,%p), specified file is (null).",
                                    pixel_type(),width,height,depth,dim,data);
      cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"w");
      cimg_std::fprintf(nfile,"%u %u %u %u\n",width,height,depth,dim);
      const T* ptrs = data;
      cimg_forYZV(*this,y,z,v) {
        cimg_forX(*this,x) cimg_std::fprintf(nfile,"%g ",(double)*(ptrs++));
        cimg_std::fputc('\n',nfile);
      }
      if (!file) cimg::fclose(nfile);
      return *this;
    }

    //! Save the image as an ASCII file (ASCII Raw + simple header).
    const CImg<T>& save_ascii(const char *const filename) const {
      return _save_ascii(0,filename);
    }

    //! Save the image as an ASCII file (ASCII Raw + simple header).
    const CImg<T>& save_ascii(cimg_std::FILE *const file) const {
      return _save_ascii(file,0);
    }

    // Save the image as a C or CPP source file (internal).
    const CImg<T>& _save_cpp(cimg_std::FILE *const file, const char *const filename) const {
      if (!file && !filename)
        throw CImgArgumentException("CImg<%s>::save_cpp() : Instance image (%u,%u,%u,%u,%p), specified file is (null).",
                                    pixel_type(),width,height,depth,dim,data);
      if (is_empty())
        throw CImgInstanceException("CImg<%s>::save_cpp() : File '%s', instance image (%u,%u,%u,%u,%p) is empty.",
                                    pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data);
      cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"w");
      char varname[1024] = { 0 };
      if (filename) cimg_std::sscanf(cimg::basename(filename),"%1023[a-zA-Z0-9_]",varname);
      if (varname[0]=='\0') cimg_std::sprintf(varname,"unnamed");
      cimg_std::fprintf(nfile,
                   "/* Define image '%s' of size %ux%ux%ux%u and type '%s' */\n"
                   "%s data_%s[] = { \n  ",
                   varname,width,height,depth,dim,pixel_type(),pixel_type(),varname);
      for (unsigned long off = 0, siz = size()-1; off<=siz; ++off) {
        cimg_std::fprintf(nfile,cimg::type<T>::format(),cimg::type<T>::format((*this)[off]));
        if (off==siz) cimg_std::fprintf(nfile," };\n");
        else if (!((off+1)%16)) cimg_std::fprintf(nfile,",\n  ");
        else cimg_std::fprintf(nfile,", ");
      }
      if (!file) cimg::fclose(nfile);
      return *this;
    }

    //! Save the image as a CPP source file.
    const CImg<T>& save_cpp(const char *const filename) const {
      return _save_cpp(0,filename);
    }

    //! Save the image as a CPP source file.
    const CImg<T>& save_cpp(cimg_std::FILE *const file) const {
      return _save_cpp(file,0);
    }

    // Save the image as a DLM file (internal).
    const CImg<T>& _save_dlm(cimg_std::FILE *const file, const char *const filename) const {
      if (is_empty())
        throw CImgInstanceException("CImg<%s>::save_dlm() : File '%s', instance image (%u,%u,%u,%u,%p) is empty.",
                                    pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data);
      if (!file && !filename)
        throw CImgArgumentException("CImg<%s>::save_dlm() : Instance image (%u,%u,%u,%u,%p), specified file is (null).",
                                    pixel_type(),width,height,depth,dim,data);
      if (depth>1)
        cimg::warn("CImg<%s>::save_dlm() : File '%s', instance image (%u,%u,%u,%u,%p) is volumetric. Pixel values along Z will be unrolled.",
                   pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data);
      if (dim>1)
        cimg::warn("CImg<%s>::save_dlm() : File '%s', instance image (%u,%u,%u,%u,%p) is multispectral. "
                   "Pixel values along V will be unrolled.",
                   pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data);

      cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"w");
      const T* ptrs = data;
      cimg_forYZV(*this,y,z,v) {
        cimg_forX(*this,x) cimg_std::fprintf(nfile,"%g%s",(double)*(ptrs++),(x==dimx()-1)?"":",");
        cimg_std::fputc('\n',nfile);
      }
      if (!file) cimg::fclose(nfile);
      return *this;
    }

    //! Save the image as a DLM file.
    const CImg<T>& save_dlm(const char *const filename) const {
      return _save_dlm(0,filename);
    }

    //! Save the image as a DLM file.
    const CImg<T>& save_dlm(cimg_std::FILE *const file) const {
      return _save_dlm(file,0);
    }

   // Save the image as a BMP file (internal).
    const CImg<T>& _save_bmp(cimg_std::FILE *const file, const char *const filename) const {
      if (is_empty())
        throw CImgInstanceException("CImg<%s>::save_bmp() : File '%s', instance image (%u,%u,%u,%u,%p) is empty.",
                                    pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data);
      if (!file && !filename)
        throw CImgArgumentException("CImg<%s>::save_bmp() : Instance image (%u,%u,%u,%u,%p), specified file is (null).",
                                    pixel_type(),width,height,depth,dim,data);
      if (depth>1)
        cimg::warn("CImg<%s>::save_bmp() : File '%s', instance image (%u,%u,%u,%u,%p) is volumetric. Only the first slice will be saved.",
                   pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data);
      if (dim>3)
        cimg::warn("CImg<%s>::save_bmp() : File '%s', instance image (%u,%u,%u,%u,%p) is multispectral. Only the three first channels will be saved.",
                   pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data);

      cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"wb");
      unsigned char header[54] = { 0 }, align_buf[4] = { 0 };
      const unsigned int
        align = (4 - (3*width)%4)%4,
        buf_size = (3*width+align)*dimy(),
        file_size = 54 + buf_size;
      header[0] = 'B'; header[1] = 'M';
      header[0x02] = file_size&0xFF;
      header[0x03] = (file_size>>8)&0xFF;
      header[0x04] = (file_size>>16)&0xFF;
      header[0x05] = (file_size>>24)&0xFF;
      header[0x0A] = 0x36;
      header[0x0E] = 0x28;
      header[0x12] = width&0xFF;
      header[0x13] = (width>>8)&0xFF;
      header[0x14] = (width>>16)&0xFF;
      header[0x15] = (width>>24)&0xFF;
      header[0x16] = height&0xFF;
      header[0x17] = (height>>8)&0xFF;
      header[0x18] = (height>>16)&0xFF;
      header[0x19] = (height>>24)&0xFF;
      header[0x1A] = 1;
      header[0x1B] = 0;
      header[0x1C] = 24;
      header[0x1D] = 0;
      header[0x22] = buf_size&0xFF;
      header[0x23] = (buf_size>>8)&0xFF;
      header[0x24] = (buf_size>>16)&0xFF;
      header[0x25] = (buf_size>>24)&0xFF;
      header[0x27] = 0x1;
      header[0x2B] = 0x1;
      cimg::fwrite(header,54,nfile);

      const T
        *pR = ptr(0,height-1,0,0),
        *pG = (dim>=2)?ptr(0,height-1,0,1):0,
        *pB = (dim>=3)?ptr(0,height-1,0,2):0;

      switch (dim) {
      case 1 : {
        cimg_forY(*this,y) { cimg_forX(*this,x) {
          const unsigned char val = (unsigned char)*(pR++);
          cimg_std::fputc(val,nfile); cimg_std::fputc(val,nfile); cimg_std::fputc(val,nfile);
        }
        cimg::fwrite(align_buf,align,nfile);
        pR-=2*width;
        }} break;
      case 2 : {
        cimg_forY(*this,y) { cimg_forX(*this,x) {
          cimg_std::fputc(0,nfile);
          cimg_std::fputc((unsigned char)(*(pG++)),nfile);
          cimg_std::fputc((unsigned char)(*(pR++)),nfile);
        }
        cimg::fwrite(align_buf,align,nfile);
        pR-=2*width; pG-=2*width;
        }} break;
      default : {
        cimg_forY(*this,y) { cimg_forX(*this,x) {
          cimg_std::fputc((unsigned char)(*(pB++)),nfile);
          cimg_std::fputc((unsigned char)(*(pG++)),nfile);
          cimg_std::fputc((unsigned char)(*(pR++)),nfile);
        }
        cimg::fwrite(align_buf,align,nfile);
        pR-=2*width; pG-=2*width; pB-=2*width;
        }
      }
      }
      if (!file) cimg::fclose(nfile);
      return *this;
    }

    //! Save the image as a BMP file.
    const CImg<T>& save_bmp(const char *const filename) const {
      return _save_bmp(0,filename);
    }

    //! Save the image as a BMP file.
    const CImg<T>& save_bmp(cimg_std::FILE *const file) const {
      return _save_bmp(file,0);
    }

    // Save a file in JPEG format (internal).
    const CImg<T>& _save_jpeg(cimg_std::FILE *const file, const char *const filename, const unsigned int quality) const {
      if (is_empty())
        throw CImgInstanceException("CImg<%s>::save_jpeg() : File '%s', instance image (%u,%u,%u,%u,%p) is empty.",
                                    pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data);
      if (!file && !filename)
        throw CImgArgumentException("CImg<%s>::save_jpeg() : Instance image (%u,%u,%u,%u,%p), specified filename is (null).",
                                    pixel_type(),width,height,depth,dim,data);
      if (depth>1)
        cimg::warn("CImg<%s>::save_jpeg() : File '%s, instance image (%u,%u,%u,%u,%p) is volumetric. Only the first slice will be saved.",
                   pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data);
#ifndef cimg_use_jpeg
      if (!file) return save_other(filename,quality);
      else throw CImgIOException("CImg<%s>::save_jpeg() : Cannot save a JPEG image in a *FILE output. Use libjpeg instead.",
                                 pixel_type());
#else
      // Fill pixel buffer
      unsigned char *buf;
      unsigned int dimbuf = 0;
      J_COLOR_SPACE colortype = JCS_RGB;
      switch (dim) {
      case 1 : { // Greyscale images
        unsigned char *buf2 = buf = new unsigned char[width*height*(dimbuf=1)];
        colortype = JCS_GRAYSCALE;
        const T *ptr_g = data;
        cimg_forXY(*this,x,y) *(buf2++) = (unsigned char)*(ptr_g++);
      } break;
      case 2 : { // RG images
        unsigned char *buf2 = buf = new unsigned char[width*height*(dimbuf=3)];
        const T *ptr_r = ptr(0,0,0,0), *ptr_g = ptr(0,0,0,1);
        colortype = JCS_RGB;
        cimg_forXY(*this,x,y) {
          *(buf2++) = (unsigned char)*(ptr_r++);
          *(buf2++) = (unsigned char)*(ptr_g++);
          *(buf2++) = 0;
        }
      } break;
      case 3 : { // RGB images
        unsigned char *buf2 = buf = new unsigned char[width*height*(dimbuf=3)];
        const T *ptr_r = ptr(0,0,0,0), *ptr_g = ptr(0,0,0,1), *ptr_b = ptr(0,0,0,2);
        colortype = JCS_RGB;
        cimg_forXY(*this,x,y) {
          *(buf2++) = (unsigned char)*(ptr_r++);
          *(buf2++) = (unsigned char)*(ptr_g++);
          *(buf2++) = (unsigned char)*(ptr_b++);
        }
      } break;
      default : { // CMYK images
        unsigned char *buf2 = buf = new unsigned char[width*height*(dimbuf=4)];
        const T *ptr_r = ptr(0,0,0,0), *ptr_g = ptr(0,0,0,1), *ptr_b = ptr(0,0,0,2), *ptr_a = ptr(0,0,0,3);
        colortype = JCS_CMYK;
        cimg_forXY(*this,x,y) {
          *(buf2++) = (unsigned char)*(ptr_r++);
          *(buf2++) = (unsigned char)*(ptr_g++);
          *(buf2++) = (unsigned char)*(ptr_b++);
          *(buf2++) = (unsigned char)*(ptr_a++);
        }
      }
      }

      // Call libjpeg functions
      struct jpeg_compress_struct cinfo;
      struct jpeg_error_mgr jerr;
      cinfo.err = jpeg_std_error(&jerr);
      jpeg_create_compress(&cinfo);
      cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"wb");
      jpeg_stdio_dest(&cinfo,nfile);
      cinfo.image_width = width;
      cinfo.image_height = height;
      cinfo.input_components = dimbuf;
      cinfo.in_color_space = colortype;
      jpeg_set_defaults(&cinfo);
      jpeg_set_quality(&cinfo,quality<100?quality:100,TRUE);
      jpeg_start_compress(&cinfo,TRUE);

      const unsigned int row_stride = width*dimbuf;
      JSAMPROW row_pointer[1];
      while (cinfo.next_scanline < cinfo.image_height) {
        row_pointer[0] = &buf[cinfo.next_scanline*row_stride];
        jpeg_write_scanlines(&cinfo,row_pointer,1);
      }
      jpeg_finish_compress(&cinfo);

      delete[] buf;
      if (!file) cimg::fclose(nfile);
      jpeg_destroy_compress(&cinfo);
      return *this;
#endif
    }

    //! Save a file in JPEG format.
    const CImg<T>& save_jpeg(const char *const filename, const unsigned int quality=100) const {
      return _save_jpeg(0,filename,quality);
    }

    //! Save a file in JPEG format.
    const CImg<T>& save_jpeg(cimg_std::FILE *const file, const unsigned int quality=100) const {
      return _save_jpeg(file,0,quality);
    }

    //! Save the image using built-in ImageMagick++ library.
    const CImg<T>& save_magick(const char *const filename) const {
      if (is_empty())
        throw CImgInstanceException("CImg<%s>::save_magick() : File '%s', instance image (%u,%u,%u,%u,%p) is empty.",
                                    pixel_type(),filename?filename:"(null)",width,height,depth,dim,data);
      if (!filename)
        throw CImgArgumentException("CImg<%s>::save_magick() : Instance image (%u,%u,%u,%u,%p), specified file is (null).",
                                    pixel_type(),width,height,depth,dim,data);
#ifdef cimg_use_magick
      Magick::Image image(Magick::Geometry(width,height),"black");
      image.type(Magick::TrueColorType);
      const T
        *rdata = ptr(0,0,0,0),
        *gdata = dim>1?ptr(0,0,0,1):0,
        *bdata = dim>2?ptr(0,0,0,2):0;
      Magick::PixelPacket *pixels = image.getPixels(0,0,width,height);
      switch (dim) {
      case 1 : // Scalar images
        for (unsigned int off = width*height; off; --off) {
          pixels->red = pixels->green = pixels->blue = Magick::Color::scaleDoubleToQuantum(*(rdata++)/255.0);
          ++pixels;
        }
        break;
      case 2 : // RG images
        for (unsigned int off = width*height; off; --off) {
          pixels->red = Magick::Color::scaleDoubleToQuantum(*(rdata++)/255.0);
          pixels->green = Magick::Color::scaleDoubleToQuantum(*(gdata++)/255.0);
          pixels->blue = 0;
          ++pixels;
        }
        break;
      default : // RGB images
        for (unsigned int off = width*height; off; --off) {
          pixels->red = Magick::Color::scaleDoubleToQuantum(*(rdata++)/255.0);
          pixels->green = Magick::Color::scaleDoubleToQuantum(*(gdata++)/255.0);
          pixels->blue = Magick::Color::scaleDoubleToQuantum(*(bdata++)/255.0);
          ++pixels;
        }
      }
      image.syncPixels();
      image.write(filename);
#else
      throw CImgIOException("CImg<%s>::save_magick() : File '%s', Magick++ library has not been linked.",
                            pixel_type(),filename);
#endif
      return *this;
    }

    // Save an image to a PNG file (internal).
    // Most of this function has been written by Eric Fausett
    const CImg<T>& _save_png(cimg_std::FILE *const file, const char *const filename) const {
      if (is_empty())
        throw CImgInstanceException("CImg<%s>::save_png() : File '%s', instance image (%u,%u,%u,%u,%p) is empty.",
                                    pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data);
      if (!filename)
        throw CImgArgumentException("CImg<%s>::save_png() : Instance image (%u,%u,%u,%u,%p), specified filename is (null).",
                                    pixel_type(),width,height,depth,dim,data);
      if (depth>1)
        cimg::warn("CImg<%s>::save_png() : File '%s', instance image (%u,%u,%u,%u,%p) is volumetric. Only the first slice will be saved.",
                   pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data);
#ifndef cimg_use_png
      if (!file) return save_other(filename);
      else throw CImgIOException("CImg<%s>::save_png() : Cannot save a PNG image in a *FILE output. You must use 'libpng' to do this instead.",
                                 pixel_type());
#else
      const char *volatile nfilename = filename; // two 'volatile' here to remove a g++ warning due to 'setjmp'.
      cimg_std::FILE *volatile nfile = file?file:cimg::fopen(nfilename,"wb");

      // 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<%s>::save_png() : File '%s', error when initializing 'png_ptr' data structure.",
                              pixel_type(),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<%s>::save_png() : File '%s', error when initializing 'info_ptr' data structure.",
                              pixel_type(),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<%s>::save_png() : File '%s', unknown fatal error.",
                              pixel_type(),nfilename?nfilename:"(FILE*)");
      }
      png_init_io(png_ptr, nfile);
      png_uint_32 width = dimx(), height = dimy();
      float vmin, vmax = (float)maxmin(vmin);
      const int bit_depth = (vmin<0 || vmax>=256)?16:8;
      int color_type;
      switch (dimv()) {
      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 = dimv()>4?4:dimv();
      const int pixel_bit_depth_flag = numChan * (bit_depth-1);

      // Allocate Memory for Image Save and Fill pixel data
      png_bytep *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 = ptr(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 = ptr(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 = ptr(0,0,0,1), *pC2 = ptr(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 = ptr(0,0,0,1), *pC2 = ptr(0,0,0,2), *pC3 = ptr(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 = ptr(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 = ptr(0,0,0,1), *pC2 = ptr(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 = ptr(0,0,0,1), *pC2 = ptr(0,0,0,2), *pC3 = ptr(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<%s>::save_png() : File '%s', unknown fatal error.",
                              pixel_type(),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
    }

    //! Save a file in PNG format
    const CImg<T>& save_png(const char *const filename) const {
      return _save_png(0,filename);
    }

    //! Save a file in PNG format
    const CImg<T>& save_png(cimg_std::FILE *const file) const {
      return _save_png(file,0);
    }

    // Save the image as a PNM file (internal function).
    const CImg<T>& _save_pnm(cimg_std::FILE *const file, const char *const filename) const {
      if (!file && !filename)
        throw CImgArgumentException("CImg<%s>::save_pnm() : Instance image (%u,%u,%u,%u,%p), specified file is (null).",
                                    pixel_type(),width,height,depth,dim,data);
      if (is_empty())
        throw CImgInstanceException("CImg<%s>::save_pnm() : File '%s', instance image (%u,%u,%u,%u,%p) is empty.",
                                    pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data);
      double stmin, stmax = (double)maxmin(stmin);
      if (depth>1)
        cimg::warn("CImg<%s>::save_pnm() : File '%s', instance image (%u,%u,%u,%u,%p) is volumetric. Only the first slice will be saved.",
                 pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data);
      if (dim>3)
        cimg::warn("CImg<%s>::save_pnm() : File '%s', instance image (%u,%u,%u,%u,%p) is multispectral. Only the three first channels will be saved.",
                 pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data);
      if (stmin<0 || stmax>65535)
        cimg::warn("CImg<%s>::save_pnm() : File '%s', instance image (%u,%u,%u,%u,%p) has pixel values in [%g,%g]. Probable type overflow.",
                   pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data,stmin,stmax);
      cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"wb");
      const T
        *ptrR = ptr(0,0,0,0),
        *ptrG = (dim>=2)?ptr(0,0,0,1):0,
        *ptrB = (dim>=3)?ptr(0,0,0,2):0;
      const unsigned int buf_size = width*height*(dim==1?1:3);

      cimg_std::fprintf(nfile,"P%c\n# CREATOR: CImg Library (original size = %ux%ux%ux%u)\n%u %u\n%u\n",
                   (dim==1?'5':'6'),width,height,depth,dim,width,height,stmax<256?255:(stmax<4096?4095:65535));

      switch (dim) {
      case 1 : { // Scalar image
        if (stmax<256) { // Binary PGM 8 bits
          unsigned char *ptrd = new unsigned char[buf_size], *xptrd = ptrd;
          cimg_forXY(*this,x,y) *(xptrd++) = (unsigned char)*(ptrR++);
          cimg::fwrite(ptrd,buf_size,nfile);
          delete[] ptrd;
        } else {             // Binary PGM 16 bits
          unsigned short *ptrd = new unsigned short[buf_size], *xptrd = ptrd;
          cimg_forXY(*this,x,y) *(xptrd++) = (unsigned short)*(ptrR++);
          if (!cimg::endianness()) cimg::invert_endianness(ptrd,buf_size);
          cimg::fwrite(ptrd,buf_size,nfile);
          delete[] ptrd;
        }
      } break;
      case 2 : { // RG image
        if (stmax<256) { // Binary PPM 8 bits
          unsigned char *ptrd = new unsigned char[buf_size], *xptrd = ptrd;
          cimg_forXY(*this,x,y) {
            *(xptrd++) = (unsigned char)*(ptrR++);
            *(xptrd++) = (unsigned char)*(ptrG++);
            *(xptrd++) = 0;
          }
          cimg::fwrite(ptrd,buf_size,nfile);
          delete[] ptrd;
        } else {             // Binary PPM 16 bits
          unsigned short *ptrd = new unsigned short[buf_size], *xptrd = ptrd;
          cimg_forXY(*this,x,y) {
            *(xptrd++) = (unsigned short)*(ptrR++);
            *(xptrd++) = (unsigned short)*(ptrG++);
            *(xptrd++) = 0;
          }
          if (!cimg::endianness()) cimg::invert_endianness(ptrd,buf_size);
          cimg::fwrite(ptrd,buf_size,nfile);
          delete[] ptrd;
        }
      } break;
      default : { // RGB image
        if (stmax<256) { // Binary PPM 8 bits
          unsigned char *ptrd = new unsigned char[buf_size], *xptrd = ptrd;
          cimg_forXY(*this,x,y) {
            *(xptrd++) = (unsigned char)*(ptrR++);
            *(xptrd++) = (unsigned char)*(ptrG++);
            *(xptrd++) = (unsigned char)*(ptrB++);
          }
          cimg::fwrite(ptrd,buf_size,nfile);
          delete[] ptrd;
        } else {             // Binary PPM 16 bits
          unsigned short *ptrd = new unsigned short[buf_size], *xptrd = ptrd;
          cimg_forXY(*this,x,y) {
            *(xptrd++) = (unsigned short)*(ptrR++);
            *(xptrd++) = (unsigned short)*(ptrG++);
            *(xptrd++) = (unsigned short)*(ptrB++);
          }
          if (!cimg::endianness()) cimg::invert_endianness(ptrd,buf_size);
          cimg::fwrite(ptrd,buf_size,nfile);
          delete[] ptrd;
        }
      }
      }
      if (!file) cimg::fclose(nfile);
      return *this;
    }

    //! Save the image as a PNM file.
    const CImg<T>& save_pnm(const char *const filename) const {
      return _save_pnm(0,filename);
    }

    //! Save the image as a PNM file.
    const CImg<T>& save_pnm(cimg_std::FILE *const file) const {
      return _save_pnm(file,0);
    }

    // Save the image as a RGB file (internal).
    const CImg<T>& _save_rgb(cimg_std::FILE *const file, const char *const filename) const {
      if (is_empty())
        throw CImgInstanceException("CImg<%s>::save_rgb() : File '%s', instance image (%u,%u,%u,%u,%p) is empty.",
                                    pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data);
      if (!file && !filename)
        throw CImgArgumentException("CImg<%s>::save_rgb() : Instance image (%u,%u,%u,%u,%p), specified file is (null).",
                                    pixel_type(),width,height,depth,dim,data);
      if (dim!=3)
        cimg::warn("CImg<%s>::save_rgb() : File '%s', instance image (%u,%u,%u,%u,%p) has not exactly 3 channels.",
                   pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data);
      cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"wb");
      const unsigned int wh = width*height;
      unsigned char *buffer = new unsigned char[3*wh], *nbuffer=buffer;
      const T
        *ptr1 = ptr(0,0,0,0),
        *ptr2 = dim>1?ptr(0,0,0,1):0,
        *ptr3 = dim>2?ptr(0,0,0,2):0;
      switch (dim) {
      case 1 : { // Scalar image
        for (unsigned int k=0; k<wh; ++k) {
          const unsigned char val = (unsigned char)*(ptr1++);
          *(nbuffer++) = val;
          *(nbuffer++) = val;
          *(nbuffer++) = val;
        }} break;
      case 2 : { // RG image
        for (unsigned int k=0; k<wh; ++k) {
          *(nbuffer++) = (unsigned char)(*(ptr1++));
          *(nbuffer++) = (unsigned char)(*(ptr2++));
          *(nbuffer++) = 0;
        }} break;
      default : { // RGB image
        for (unsigned int k=0; k<wh; ++k) {
          *(nbuffer++) = (unsigned char)(*(ptr1++));
          *(nbuffer++) = (unsigned char)(*(ptr2++));
          *(nbuffer++) = (unsigned char)(*(ptr3++));
        }
      }
      }
      cimg::fwrite(buffer,3*wh,nfile);
      if (!file) cimg::fclose(nfile);
      delete[] buffer;
      return *this;
    }

    //! Save the image as a RGB file.
    const CImg<T>& save_rgb(const char *const filename) const {
      return _save_rgb(0,filename);
    }

    //! Save the image as a RGB file.
    const CImg<T>& save_rgb(cimg_std::FILE *const file) const {
      return _save_rgb(file,0);
    }

    // Save the image as a RGBA file (internal).
    const CImg<T>& _save_rgba(cimg_std::FILE *const file, const char *const filename) const {
      if (is_empty())
        throw CImgInstanceException("CImg<%s>::save_rgba() : File '%s', instance image (%u,%u,%u,%u,%p) is empty.",
                                    pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data);
      if (!file && !filename)
        throw CImgArgumentException("CImg<%s>::save_rgba() : Instance image (%u,%u,%u,%u,%p), specified file is (null).",
                                    pixel_type(),width,height,depth,dim,data);
      if (dim!=4)
        cimg::warn("CImg<%s>::save_rgba() : File '%s, instance image (%u,%u,%u,%u,%p) has not exactly 4 channels.",
                   pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data);
      cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"wb");
      const unsigned int wh = width*height;
      unsigned char *buffer = new unsigned char[4*wh], *nbuffer=buffer;
      const T
        *ptr1 = ptr(0,0,0,0),
        *ptr2 = dim>1?ptr(0,0,0,1):0,
        *ptr3 = dim>2?ptr(0,0,0,2):0,
        *ptr4 = dim>3?ptr(0,0,0,3):0;
      switch (dim) {
      case 1 : { // Scalar images
        for (unsigned int k=0; k<wh; ++k) {
          const unsigned char val = (unsigned char)*(ptr1++);
          *(nbuffer++) = val;
          *(nbuffer++) = val;
          *(nbuffer++) = val;
          *(nbuffer++) = 255;
        }} break;
      case 2 : { // RG images
        for (unsigned int k=0; k<wh; ++k) {
          *(nbuffer++) = (unsigned char)(*(ptr1++));
          *(nbuffer++) = (unsigned char)(*(ptr2++));
          *(nbuffer++) = 0;
          *(nbuffer++) = 255;
        }} break;
      case 3 : { // RGB images
        for (unsigned int k=0; k<wh; ++k) {
          *(nbuffer++) = (unsigned char)(*(ptr1++));
          *(nbuffer++) = (unsigned char)(*(ptr2++));
          *(nbuffer++) = (unsigned char)(*(ptr3++));
          *(nbuffer++) = 255;
        }} break;
      default : { // RGBA images
        for (unsigned int k=0; k<wh; ++k) {
          *(nbuffer++) = (unsigned char)(*(ptr1++));
          *(nbuffer++) = (unsigned char)(*(ptr2++));
          *(nbuffer++) = (unsigned char)(*(ptr3++));
          *(nbuffer++) = (unsigned char)(*(ptr4++));
        }
      }
      }
      cimg::fwrite(buffer,4*wh,nfile);
      if (!file) cimg::fclose(nfile);
      delete[] buffer;
      return *this;
    }

    //! Save the image as a RGBA file.
    const CImg<T>& save_rgba(const char *const filename) const {
      return _save_rgba(0,filename);
    }

    //! Save the image as a RGBA file.
    const CImg<T>& save_rgba(cimg_std::FILE *const file) const {
      return _save_rgba(file,0);
    }

    // Save a plane into a tiff file
#ifdef cimg_use_tiff
    const CImg<T>& _save_tiff(TIFF *tif, const unsigned int directory) const {
      if (is_empty() || !tif) return *this;
      const char *const filename = TIFFFileName(tif);
      uint32 rowsperstrip = (uint32)-1;
      uint16 spp = dim, bpp = sizeof(T)*8, photometric, compression = COMPRESSION_NONE;
      if (spp==3 || spp==4) photometric = PHOTOMETRIC_RGB;
      else photometric = PHOTOMETRIC_MINISBLACK;
      TIFFSetDirectory(tif,directory);
      TIFFSetField(tif,TIFFTAG_IMAGEWIDTH,width);
      TIFFSetField(tif,TIFFTAG_IMAGELENGTH,height);
      TIFFSetField(tif,TIFFTAG_ORIENTATION,ORIENTATION_TOPLEFT);
      TIFFSetField(tif,TIFFTAG_SAMPLESPERPIXEL,spp);
      if (cimg::type<T>::is_float()) TIFFSetField(tif,TIFFTAG_SAMPLEFORMAT,3);
      else if (cimg::type<T>::min()==0) TIFFSetField(tif,TIFFTAG_SAMPLEFORMAT,1);
      else TIFFSetField(tif,TIFFTAG_SAMPLEFORMAT,2);
      TIFFSetField(tif,TIFFTAG_BITSPERSAMPLE,bpp);
      TIFFSetField(tif,TIFFTAG_PLANARCONFIG,PLANARCONFIG_CONTIG);
      TIFFSetField(tif,TIFFTAG_PHOTOMETRIC,photometric);
      TIFFSetField(tif,TIFFTAG_COMPRESSION,compression);
      rowsperstrip = TIFFDefaultStripSize(tif,rowsperstrip);
      TIFFSetField(tif,TIFFTAG_ROWSPERSTRIP,rowsperstrip);
      TIFFSetField(tif,TIFFTAG_FILLORDER,FILLORDER_MSB2LSB);
      TIFFSetField(tif,TIFFTAG_SOFTWARE,"CImg");
      T *buf = (T*)_TIFFmalloc(TIFFStripSize(tif));
      if (buf){
        for (unsigned int row = 0; row<height; row+=rowsperstrip) {
          uint32 nrow = (row+rowsperstrip>height?height-row:rowsperstrip);
          tstrip_t strip = TIFFComputeStrip(tif,row,0);
          tsize_t i = 0;
          for (unsigned int rr = 0; rr<nrow; ++rr)
            for (unsigned int cc = 0; cc<width; ++cc)
              for (unsigned int vv = 0; vv<spp; ++vv)
                buf[i++] = (*this)(cc,row+rr,vv);
          if (TIFFWriteEncodedStrip(tif,strip,buf,i*sizeof(T))<0)
            throw CImgException("CImg<%s>::save_tiff() : File '%s', an error has occured while writing a strip.",
                                pixel_type(),filename?filename:"(FILE*)");
        }
        _TIFFfree(buf);
      }
      TIFFWriteDirectory(tif);
      return (*this);
    }
#endif

    //! Save a file in TIFF format.
    const CImg<T>& save_tiff(const char *const filename) const {
      if (is_empty())
        throw CImgInstanceException("CImg<%s>::save_tiff() : File '%s', instance image (%u,%u,%u,%u,%p) is empty.",
                                    pixel_type(),filename?filename:"(null)",width,height,depth,dim,data);
      if (!filename)
        throw CImgArgumentException("CImg<%s>::save_tiff() : Specified filename is (null) for instance image (%u,%u,%u,%u,%p).",
                                    pixel_type(),width,height,depth,dim,data);
#ifdef cimg_use_tiff
      TIFF *tif = TIFFOpen(filename,"w");
      if (tif) {
        cimg_forZ(*this,z) get_slice(z)._save_tiff(tif,z);
        TIFFClose(tif);
      } else throw CImgException("CImg<%s>::save_tiff() : File '%s', error while opening file stream for writing.",
                                 pixel_type(),filename);
#else
      return save_other(filename);
#endif
      return *this;
    }

    //! Save the image as an ANALYZE7.5 or NIFTI file.
    const CImg<T>& save_analyze(const char *const filename, const float *const voxsize=0) const {
      if (is_empty())
        throw CImgInstanceException("CImg<%s>::save_analyze() : File '%s', instance image (%u,%u,%u,%u,%p) is empty.",
                                    pixel_type(),filename?filename:"(null)",width,height,depth,dim,data);
      if (!filename)
        throw CImgArgumentException("CImg<%s>::save_analyze() :  Instance image (%u,%u,%u,%u,%p), specified filename is (null).",
                                    pixel_type(),width,height,depth,dim,data);
      cimg_std::FILE *file;
      char header[348], hname[1024], iname[1024];
      const char *ext = cimg::split_filename(filename);
      short datatype=-1;
      cimg_std::memset(header,0,348);
      if (!ext[0]) { cimg_std::sprintf(hname,"%s.hdr",filename); cimg_std::sprintf(iname,"%s.img",filename); }
      if (!cimg::strncasecmp(ext,"hdr",3)) {
        cimg_std::strcpy(hname,filename); cimg_std::strcpy(iname,filename); cimg_std::sprintf(iname+cimg::strlen(iname)-3,"img");
      }
      if (!cimg::strncasecmp(ext,"img",3)) {
        cimg_std::strcpy(hname,filename); cimg_std::strcpy(iname,filename); cimg_std::sprintf(hname+cimg::strlen(iname)-3,"hdr");
      }
      if (!cimg::strncasecmp(ext,"nii",3)) {
        cimg_std::strcpy(hname,filename); iname[0] = 0;
      }
      ((int*)(header))[0] = 348;
      cimg_std::sprintf(header+4,"CImg");
      cimg_std::sprintf(header+14," ");
      ((short*)(header+36))[0] = 4096;
      ((char*)(header+38))[0] = 114;
      ((short*)(header+40))[0] = 4;
      ((short*)(header+40))[1] = width;
      ((short*)(header+40))[2] = height;
      ((short*)(header+40))[3] = depth;
      ((short*)(header+40))[4] = dim;
      if (!cimg::strcasecmp(pixel_type(),"bool"))           datatype = 2;
      if (!cimg::strcasecmp(pixel_type(),"unsigned char"))  datatype = 2;
      if (!cimg::strcasecmp(pixel_type(),"char"))           datatype = 2;
      if (!cimg::strcasecmp(pixel_type(),"unsigned short")) datatype = 4;
      if (!cimg::strcasecmp(pixel_type(),"short"))          datatype = 4;
      if (!cimg::strcasecmp(pixel_type(),"unsigned int"))   datatype = 8;
      if (!cimg::strcasecmp(pixel_type(),"int"))            datatype = 8;
      if (!cimg::strcasecmp(pixel_type(),"unsigned long"))  datatype = 8;
      if (!cimg::strcasecmp(pixel_type(),"long"))           datatype = 8;
      if (!cimg::strcasecmp(pixel_type(),"float"))          datatype = 16;
      if (!cimg::strcasecmp(pixel_type(),"double"))         datatype = 64;
      if (datatype<0)
        throw CImgIOException("CImg<%s>::save_analyze() : Cannot save image '%s' since pixel type (%s)"
                              "is not handled in Analyze7.5 specifications.\n",
                              pixel_type(),filename,pixel_type());
      ((short*)(header+70))[0] = datatype;
      ((short*)(header+72))[0] = sizeof(T);
      ((float*)(header+112))[0] = 1;
      ((float*)(header+76))[0] = 0;
      if (voxsize) {
        ((float*)(header+76))[1] = voxsize[0];
        ((float*)(header+76))[2] = voxsize[1];
        ((float*)(header+76))[3] = voxsize[2];
      } else ((float*)(header+76))[1] = ((float*)(header+76))[2] = ((float*)(header+76))[3] = 1;
      file = cimg::fopen(hname,"wb");
      cimg::fwrite(header,348,file);
      if (iname[0]) { cimg::fclose(file); file = cimg::fopen(iname,"wb"); }
      cimg::fwrite(data,size(),file);
      cimg::fclose(file);
      return *this;
    }

    //! Save the image as a .cimg file.
    const CImg<T>& save_cimg(const char *const filename, const bool compress=false) const {
      CImgList<T>(*this,true).save_cimg(filename,compress);
      return *this;
    }

    // Save the image as a .cimg file.
    const CImg<T>& save_cimg(cimg_std::FILE *const file, const bool compress=false) const {
      CImgList<T>(*this,true).save_cimg(file,compress);
      return *this;
    }

    //! Insert the image into an existing .cimg file, at specified coordinates.
    const CImg<T>& save_cimg(const char *const filename,
                             const unsigned int n0,
                             const unsigned int x0, const unsigned int y0,
                             const unsigned int z0, const unsigned int v0) const {
      CImgList<T>(*this,true).save_cimg(filename,n0,x0,y0,z0,v0);
      return *this;
    }

    //! Insert the image into an existing .cimg file, at specified coordinates.
    const CImg<T>& save_cimg(cimg_std::FILE *const file,
                             const unsigned int n0,
                             const unsigned int x0, const unsigned int y0,
                             const unsigned int z0, const unsigned int v0) const {
      CImgList<T>(*this,true).save_cimg(file,n0,x0,y0,z0,v0);
      return *this;
    }

    //! Save an empty .cimg file with specified dimensions.
    static void save_empty_cimg(const char *const filename,
                                const unsigned int dx, const unsigned int dy=1,
                                const unsigned int dz=1, const unsigned int dv=1) {
      return CImgList<T>::save_empty_cimg(filename,1,dx,dy,dz,dv);
    }

    //! Save an empty .cimg file with specified dimensions.
    static void save_empty_cimg(cimg_std::FILE *const file,
                                const unsigned int dx, const unsigned int dy=1,
                                const unsigned int dz=1, const unsigned int dv=1) {
      return CImgList<T>::save_empty_cimg(file,1,dx,dy,dz,dv);
    }

    // Save the image as an INRIMAGE-4 file (internal).
    const CImg<T>& _save_inr(cimg_std::FILE *const file, const char *const filename, const float *const voxsize) const {
      if (is_empty())
        throw CImgInstanceException("CImg<%s>::save_inr() : File '%s', instance image (%u,%u,%u,%u,%p) is empty.",
                                    pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data);
      if (!filename)
        throw CImgArgumentException("CImg<%s>::save_inr() : Instance image (%u,%u,%u,%u,%p), specified file is (null).",
                                    pixel_type(),width,height,depth,dim,data);
      int inrpixsize=-1;
      const char *inrtype = "unsigned fixed\nPIXSIZE=8 bits\nSCALE=2**0";
      if (!cimg::strcasecmp(pixel_type(),"unsigned char"))  { inrtype = "unsigned fixed\nPIXSIZE=8 bits\nSCALE=2**0"; inrpixsize = 1; }
      if (!cimg::strcasecmp(pixel_type(),"char"))           { inrtype = "fixed\nPIXSIZE=8 bits\nSCALE=2**0"; inrpixsize = 1; }
      if (!cimg::strcasecmp(pixel_type(),"unsigned short")) { inrtype = "unsigned fixed\nPIXSIZE=16 bits\nSCALE=2**0";inrpixsize = 2; }
      if (!cimg::strcasecmp(pixel_type(),"short"))          { inrtype = "fixed\nPIXSIZE=16 bits\nSCALE=2**0"; inrpixsize = 2; }
      if (!cimg::strcasecmp(pixel_type(),"unsigned int"))   { inrtype = "unsigned fixed\nPIXSIZE=32 bits\nSCALE=2**0";inrpixsize = 4; }
      if (!cimg::strcasecmp(pixel_type(),"int"))            { inrtype = "fixed\nPIXSIZE=32 bits\nSCALE=2**0"; inrpixsize = 4; }
      if (!cimg::strcasecmp(pixel_type(),"float"))          { inrtype = "float\nPIXSIZE=32 bits"; inrpixsize = 4; }
      if (!cimg::strcasecmp(pixel_type(),"double"))         { inrtype = "float\nPIXSIZE=64 bits"; inrpixsize = 8; }
      if (inrpixsize<=0)
        throw CImgIOException("CImg<%s>::save_inr() : Don't know how to save images of '%s'",
                              pixel_type(),pixel_type());
      cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"wb");
      char header[257];
      int err = cimg_std::sprintf(header,"#INRIMAGE-4#{\nXDIM=%u\nYDIM=%u\nZDIM=%u\nVDIM=%u\n",width,height,depth,dim);
      if (voxsize) err += cimg_std::sprintf(header+err,"VX=%g\nVY=%g\nVZ=%g\n",voxsize[0],voxsize[1],voxsize[2]);
      err += cimg_std::sprintf(header+err,"TYPE=%s\nCPU=%s\n",inrtype,cimg::endianness()?"sun":"decm");
      cimg_std::memset(header+err,'\n',252-err);
      cimg_std::memcpy(header+252,"##}\n",4);
      cimg::fwrite(header,256,nfile);
      cimg_forXYZ(*this,x,y,z) cimg_forV(*this,k) cimg::fwrite(&((*this)(x,y,z,k)),1,nfile);
      if (!file) cimg::fclose(nfile);
      return *this;
    }

    //! Save the image as an INRIMAGE-4 file.
    const CImg<T>& save_inr(const char *const filename, const float *const voxsize=0) const {
      return _save_inr(0,filename,voxsize);
    }

    //! Save the image as an INRIMAGE-4 file.
    const CImg<T>& save_inr(cimg_std::FILE *const file, const float *const voxsize=0) const {
      return _save_inr(file,0,voxsize);
    }

    // Save the image as a PANDORE-5 file (internal).
    unsigned int _save_pandore_header_length(unsigned int id, unsigned int *dims, const unsigned int colorspace) const {
      unsigned int nbdims = 0;
      if (id==2 || id==3 || id==4)    { dims[0] = 1;   dims[1] = width;  nbdims = 2; }
      if (id==5 || id==6 || id==7)    { dims[0] = 1;   dims[1] = height; dims[2] = width;  nbdims=3; }
      if (id==8 || id==9 || id==10)   { dims[0] = dim; dims[1] = depth;  dims[2] = height; dims[3] = width; nbdims = 4; }
      if (id==16 || id==17 || id==18) { dims[0] = 3;   dims[1] = height; dims[2] = width;  dims[3] = colorspace; nbdims = 4; }
      if (id==19 || id==20 || id==21) { dims[0] = 3;   dims[1] = depth;  dims[2] = height; dims[3] = width; dims[4] = colorspace; nbdims = 5; }
      if (id==22 || id==23 || id==25) { dims[0] = dim; dims[1] = width;  nbdims = 2; }
      if (id==26 || id==27 || id==29) { dims[0] = dim; dims[1] = height; dims[2] = width;  nbdims=3; }
      if (id==30 || id==31 || id==33) { dims[0] = dim; dims[1] = depth;  dims[2] = height; dims[3] = width; nbdims = 4; }
      return nbdims;
    }

    const CImg<T>& _save_pandore(cimg_std::FILE *const file, const char *const filename, const unsigned int colorspace) const {
      typedef unsigned char uchar;
      typedef unsigned short ushort;
      typedef unsigned int uint;
      typedef unsigned long ulong;

#define __cimg_save_pandore_case(dtype) \
       dtype *buffer = new dtype[size()]; \
       const T *ptrs = data; \
       cimg_foroff(*this,off) *(buffer++) = (dtype)(*(ptrs++)); \
       buffer-=size(); \
       cimg::fwrite(buffer,size(),nfile); \
       delete[] buffer

#define _cimg_save_pandore_case(sy,sz,sv,stype,id) \
      if (!saved && (sy?(sy==height):true) && (sz?(sz==depth):true) && (sv?(sv==dim):true) && !cimg::strcmp(stype,pixel_type())) { \
        unsigned int *iheader = (unsigned int*)(header+12); \
        nbdims = _save_pandore_header_length((*iheader=id),dims,colorspace); \
        cimg::fwrite(header,36,nfile); \
        if (sizeof(ulong)==4) { ulong ndims[5]; for (int d = 0; d<5; ++d) ndims[d] = (ulong)dims[d]; cimg::fwrite(ndims,nbdims,nfile); } \
        else if (sizeof(uint)==4) { uint ndims[5]; for (int d = 0; d<5; ++d) ndims[d] = (uint)dims[d]; cimg::fwrite(ndims,nbdims,nfile); } \
        else if (sizeof(ushort)==4) { ushort ndims[5]; for (int d = 0; d<5; ++d) ndims[d] = (ushort)dims[d]; cimg::fwrite(ndims,nbdims,nfile); } \
        else throw CImgIOException("CImg<%s>::save_pandore() : File '%s', instance image (%u,%u,%u,%u,%p), output type is not" \
                                   "supported on this architecture.",pixel_type(),filename?filename:"(FILE*)",width,height, \
                                   depth,dim,data); \
        if (id==2 || id==5 || id==8 || id==16 || id==19 || id==22 || id==26 || id==30) { \
          __cimg_save_pandore_case(uchar); \
        } else if (id==3 || id==6 || id==9 || id==17 || id==20 || id==23 || id==27 || id==31) { \
          if (sizeof(ulong)==4) { __cimg_save_pandore_case(ulong); } \
          else if (sizeof(uint)==4) { __cimg_save_pandore_case(uint); } \
          else if (sizeof(ushort)==4) { __cimg_save_pandore_case(ushort); } \
          else throw CImgIOException("CImg<%s>::save_pandore() : File '%s', instance image (%u,%u,%u,%u,%p), output type is not" \
                                     "supported on this architecture.",pixel_type(),filename?filename:"(FILE*)",width,height, \
                                     depth,dim,data); \
        } else if (id==4 || id==7 || id==10 || id==18 || id==21 || id==25 || id==29 || id==33) { \
          if (sizeof(double)==4) { __cimg_save_pandore_case(double); } \
          else if (sizeof(float)==4) { __cimg_save_pandore_case(float); } \
          else throw CImgIOException("CImg<%s>::save_pandore() : File '%s', instance image (%u,%u,%u,%u,%p), output type is not" \
                                     "supported on this architecture.",pixel_type(),filename?filename:"(FILE*)",width,height, \
                                     depth,dim,data); \
        } \
        saved = true; \
      }

      if (is_empty())
        throw CImgInstanceException("CImg<%s>::save_pandore() : File '%s', instance image (%u,%u,%u,%u,%p) is empty.",
                                    pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data);
      if (!file && !filename)
        throw CImgArgumentException("CImg<%s>::save_pandore() : Instance image (%u,%u,%u,%u,%p), specified file is (null).",
                                    pixel_type(),width,height,depth,dim,data);
      cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"wb");
      unsigned char header[36] = { 'P','A','N','D','O','R','E','0','4',0,0,0,
                                   0,0,0,0,'C','I','m','g',0,0,0,0,0,'N','o',' ','d','a','t','e',0,0,0,0 };
      unsigned int nbdims, dims[5];
      bool saved = false;
      _cimg_save_pandore_case(1,1,1,"unsigned char",2);
      _cimg_save_pandore_case(1,1,1,"char",3);
      _cimg_save_pandore_case(1,1,1,"short",3);
      _cimg_save_pandore_case(1,1,1,"unsigned short",3);
      _cimg_save_pandore_case(1,1,1,"unsigned int",3);
      _cimg_save_pandore_case(1,1,1,"int",3);
      _cimg_save_pandore_case(1,1,1,"unsigned long",4);
      _cimg_save_pandore_case(1,1,1,"long",3);
      _cimg_save_pandore_case(1,1,1,"float",4);
      _cimg_save_pandore_case(1,1,1,"double",4);

      _cimg_save_pandore_case(0,1,1,"unsigned char",5);
      _cimg_save_pandore_case(0,1,1,"char",6);
      _cimg_save_pandore_case(0,1,1,"short",6);
      _cimg_save_pandore_case(0,1,1,"unsigned short",6);
      _cimg_save_pandore_case(0,1,1,"unsigned int",6);
      _cimg_save_pandore_case(0,1,1,"int",6);
      _cimg_save_pandore_case(0,1,1,"unsigned long",7);
      _cimg_save_pandore_case(0,1,1,"long",6);
      _cimg_save_pandore_case(0,1,1,"float",7);
      _cimg_save_pandore_case(0,1,1,"double",7);

      _cimg_save_pandore_case(0,0,1,"unsigned char",8);
      _cimg_save_pandore_case(0,0,1,"char",9);
      _cimg_save_pandore_case(0,0,1,"short",9);
      _cimg_save_pandore_case(0,0,1,"unsigned short",9);
      _cimg_save_pandore_case(0,0,1,"unsigned int",9);
      _cimg_save_pandore_case(0,0,1,"int",9);
      _cimg_save_pandore_case(0,0,1,"unsigned long",10);
      _cimg_save_pandore_case(0,0,1,"long",9);
      _cimg_save_pandore_case(0,0,1,"float",10);
      _cimg_save_pandore_case(0,0,1,"double",10);

      _cimg_save_pandore_case(0,1,3,"unsigned char",16);
      _cimg_save_pandore_case(0,1,3,"char",17);
      _cimg_save_pandore_case(0,1,3,"short",17);
      _cimg_save_pandore_case(0,1,3,"unsigned short",17);
      _cimg_save_pandore_case(0,1,3,"unsigned int",17);
      _cimg_save_pandore_case(0,1,3,"int",17);
      _cimg_save_pandore_case(0,1,3,"unsigned long",18);
      _cimg_save_pandore_case(0,1,3,"long",17);
      _cimg_save_pandore_case(0,1,3,"float",18);
      _cimg_save_pandore_case(0,1,3,"double",18);

      _cimg_save_pandore_case(0,0,3,"unsigned char",19);
      _cimg_save_pandore_case(0,0,3,"char",20);
      _cimg_save_pandore_case(0,0,3,"short",20);
      _cimg_save_pandore_case(0,0,3,"unsigned short",20);
      _cimg_save_pandore_case(0,0,3,"unsigned int",20);
      _cimg_save_pandore_case(0,0,3,"int",20);
      _cimg_save_pandore_case(0,0,3,"unsigned long",21);
      _cimg_save_pandore_case(0,0,3,"long",20);
      _cimg_save_pandore_case(0,0,3,"float",21);
      _cimg_save_pandore_case(0,0,3,"double",21);

      _cimg_save_pandore_case(1,1,0,"unsigned char",22);
      _cimg_save_pandore_case(1,1,0,"char",23);
      _cimg_save_pandore_case(1,1,0,"short",23);
      _cimg_save_pandore_case(1,1,0,"unsigned short",23);
      _cimg_save_pandore_case(1,1,0,"unsigned int",23);
      _cimg_save_pandore_case(1,1,0,"int",23);
      _cimg_save_pandore_case(1,1,0,"unsigned long",25);
      _cimg_save_pandore_case(1,1,0,"long",23);
      _cimg_save_pandore_case(1,1,0,"float",25);
      _cimg_save_pandore_case(1,1,0,"double",25);

      _cimg_save_pandore_case(0,1,0,"unsigned char",26);
      _cimg_save_pandore_case(0,1,0,"char",27);
      _cimg_save_pandore_case(0,1,0,"short",27);
      _cimg_save_pandore_case(0,1,0,"unsigned short",27);
      _cimg_save_pandore_case(0,1,0,"unsigned int",27);
      _cimg_save_pandore_case(0,1,0,"int",27);
      _cimg_save_pandore_case(0,1,0,"unsigned long",29);
      _cimg_save_pandore_case(0,1,0,"long",27);
      _cimg_save_pandore_case(0,1,0,"float",29);
      _cimg_save_pandore_case(0,1,0,"double",29);

      _cimg_save_pandore_case(0,0,0,"unsigned char",30);
      _cimg_save_pandore_case(0,0,0,"char",31);
      _cimg_save_pandore_case(0,0,0,"short",31);
      _cimg_save_pandore_case(0,0,0,"unsigned short",31);
      _cimg_save_pandore_case(0,0,0,"unsigned int",31);
      _cimg_save_pandore_case(0,0,0,"int",31);
      _cimg_save_pandore_case(0,0,0,"unsigned long",33);
      _cimg_save_pandore_case(0,0,0,"long",31);
      _cimg_save_pandore_case(0,0,0,"float",33);
      _cimg_save_pandore_case(0,0,0,"double",33);

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

    //! Save the image as a PANDORE-5 file.
    const CImg<T>& save_pandore(const char *const filename, const unsigned int colorspace=0) const {
      return _save_pandore(0,filename,colorspace);
    }

    //! Save the image as a PANDORE-5 file.
    const CImg<T>& save_pandore(cimg_std::FILE *const file, const unsigned int colorspace=0) const {
      return _save_pandore(file,0,colorspace);
    }

   // Save the image as a RAW file (internal).
    const CImg<T>& _save_raw(cimg_std::FILE *const file, const char *const filename, const bool multiplexed) const {
      if (is_empty())
        throw CImgInstanceException("CImg<%s>::save_raw() : File '%s', instance image (%u,%u,%u,%u,%p) is empty.",
                                    pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data);
      if (!file && !filename)
        throw CImgArgumentException("CImg<%s>::save_raw() : Instance image (%u,%u,%u,%u,%p), specified file is (null).",
                                    pixel_type(),width,height,depth,dim,data);
      cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"wb");
      if (!multiplexed) cimg::fwrite(data,size(),nfile);
      else {
        CImg<T> buf(dim);
        cimg_forXYZ(*this,x,y,z) {
          cimg_forV(*this,k) buf[k] = (*this)(x,y,z,k);
          cimg::fwrite(buf.data,dim,nfile);
        }
      }
      if (!file) cimg::fclose(nfile);
      return *this;
    }

    //! Save the image as a RAW file.
    const CImg<T>& save_raw(const char *const filename, const bool multiplexed=false) const {
      return _save_raw(0,filename,multiplexed);
    }

    //! Save the image as a RAW file.
    const CImg<T>& save_raw(cimg_std::FILE *const file, const bool multiplexed=false) const {
      return _save_raw(file,0,multiplexed);
    }

    //! Save the image as a video sequence file, using FFMPEG library.
    const CImg<T>& save_ffmpeg(const char *const filename, const unsigned int first_frame=0, const unsigned int last_frame=~0U,
                               const unsigned int fps=25) const {
      if (is_empty())
        throw CImgInstanceException("CImg<%s>::save_ffmpeg() : File '%s', instance image (%u,%u,%u,%u,%p) is empty.",
                                    pixel_type(),filename?filename:"(null)",width,height,depth,dim,data);
      if (!filename)
        throw CImgArgumentException("CImg<%s>::save_ffmpeg() : Instance image (%u,%u,%u,%u,%p), specified filename is (null).",
                                    pixel_type(),width,height,depth,dim,data);
      if (!fps)
        throw CImgArgumentException("CImg<%s>::save_ffmpeg() : File '%s', specified framerate is 0.",
                                    pixel_type(),filename);
#ifndef cimg_use_ffmpeg
      return save_ffmpeg_external(filename,first_frame,last_frame);
#else
      get_split('z').save_ffmpeg(filename,first_frame,last_frame,fps);
#endif
      return *this;
    }

    //! Save the image as a YUV video sequence file.
    const CImg<T>& save_yuv(const char *const filename, const bool rgb2yuv=true) const {
      get_split('z').save_yuv(filename,rgb2yuv);
      return *this;
    }

    //! Save the image as a YUV video sequence file.
    const CImg<T>& save_yuv(cimg_std::FILE *const file, const bool rgb2yuv=true) const {
      get_split('z').save_yuv(file,rgb2yuv);
      return *this;
    }

   // Save OFF files (internal).
    template<typename tf, typename tc>
    const CImg<T>& _save_off(cimg_std::FILE *const file, const char *const filename,
                             const CImgList<tf>& primitives, const CImgList<tc>& colors, const bool invert_faces) const {
      if (is_empty())
        throw CImgInstanceException("CImg<%s>::save_off() : File '%s', instance image (%u,%u,%u,%u,%p) is empty.",
                                    pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data);
      if (!file && !filename)
        throw CImgArgumentException("CImg<%s>::save_off() : Specified filename is (null).",
                                    pixel_type());
      if (height<3) return get_resize(-100,3,1,1,0)._save_off(file,filename,primitives,colors,invert_faces);
      CImgList<tc> _colors;
      if (!colors) _colors.insert(primitives.size,CImg<tc>::vector(200,200,200));
      const CImgList<tc>& ncolors = colors?colors:_colors;

      cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"w");
      cimg_std::fprintf(nfile,"OFF\n%u %u %u\n",width,primitives.size,3*primitives.size);
      cimg_forX(*this,i) cimg_std::fprintf(nfile,"%f %f %f\n",(float)((*this)(i,0)),(float)((*this)(i,1)),(float)((*this)(i,2)));
      cimglist_for(primitives,l) {
        const unsigned int prim = primitives[l].size();
        const bool textured = (prim>4);
        const CImg<tc>& color = ncolors[l];
        const unsigned int s = textured?color.dimv():color.size();
        const float
          r = textured?(s>0?(float)(color.get_shared_channel(0).mean()/255.0f):1.0f):(s>0?(float)(color(0)/255.0f):1.0f),
          g = textured?(s>1?(float)(color.get_shared_channel(1).mean()/255.0f):r)   :(s>1?(float)(color(1)/255.0f):r),
          b = textured?(s>2?(float)(color.get_shared_channel(2).mean()/255.0f):r)   :(s>2?(float)(color(2)/255.0f):r);

        switch (prim) {
        case 1 :
          cimg_std::fprintf(nfile,"1 %u %f %f %f\n",(unsigned int)primitives(l,0),r,g,b);
          break;
        case 2 : case 6 :
          cimg_std::fprintf(nfile,"2 %u %u %f %f %f\n",(unsigned int)primitives(l,0),(unsigned int)primitives(l,1),r,g,b);
          break;
        case 3 : case 9 :
          if (invert_faces)
            cimg_std::fprintf(nfile,"3 %u %u %u %f %f %f\n",(unsigned int)primitives(l,0),(unsigned int)primitives(l,1),(unsigned int)primitives(l,2),r,g,b);
          else
            cimg_std::fprintf(nfile,"3 %u %u %u %f %f %f\n",(unsigned int)primitives(l,0),(unsigned int)primitives(l,2),(unsigned int)primitives(l,1),r,g,b);
          break;
        case 4 : case 12 :
          if (invert_faces)
            cimg_std::fprintf(nfile,"4 %u %u %u %u %f %f %f\n",
                         (unsigned int)primitives(l,0),(unsigned int)primitives(l,1),(unsigned int)primitives(l,2),(unsigned int)primitives(l,3),r,g,b);
          else
            cimg_std::fprintf(nfile,"4 %u %u %u %u %f %f %f\n",
                         (unsigned int)primitives(l,0),(unsigned int)primitives(l,3),(unsigned int)primitives(l,2),(unsigned int)primitives(l,1),r,g,b);
          break;
        }
      }
      if (!file) cimg::fclose(nfile);
      return *this;
    }

    //! Save OFF files.
    template<typename tf, typename tc>
    const CImg<T>& save_off(const char *const filename,
                            const CImgList<tf>& primitives, const CImgList<tc>& colors, const bool invert_faces=false) const {
      return _save_off(0,filename,primitives,colors,invert_faces);
    }

    //! Save OFF files.
    template<typename tf, typename tc>
    const CImg<T>& save_off(cimg_std::FILE *const file,
                            const CImgList<tf>& primitives, const CImgList<tc>& colors, const bool invert_faces=false) const {
      return _save_off(file,0,primitives,colors,invert_faces);
    }

    //! Save the image as a video sequence file, using the external tool 'ffmpeg'.
    const CImg<T>& save_ffmpeg_external(const char *const filename, const unsigned int first_frame=0, const unsigned int last_frame=~0U,
                                        const char *const codec="mpeg2video") const {
      if (is_empty())
        throw CImgInstanceException("CImg<%s>::save_ffmpeg_external() : File '%s', instance image (%u,%u,%u,%u,%p) is empty.",
                                    pixel_type(),filename?filename:"(null)",width,height,depth,dim,data);
      if (!filename)
        throw CImgArgumentException("CImg<%s>::save_ffmpeg_external() : Instance image (%u,%u,%u,%u,%p), specified filename is (null).",
                                    pixel_type(),width,height,depth,dim,data);
      get_split('z').save_ffmpeg_external(filename,first_frame,last_frame,codec);
      return *this;
    }

    //! Save the image using GraphicsMagick's gm.
    /** Function that saves the image for other file formats that are not natively handled by CImg,
        using the tool 'gm' from the GraphicsMagick package.\n
        This is the case for all compressed image formats (GIF,PNG,JPG,TIF, ...). You need to install
        the GraphicsMagick package in order to get
        this function working properly (see http://www.graphicsmagick.org ).
    **/
    const CImg<T>& save_graphicsmagick_external(const char *const filename, const unsigned int quality=100) const {
      if (is_empty())
        throw CImgInstanceException("CImg<%s>::save_graphicsmagick_external() : File '%s', instance image (%u,%u,%u,%u,%p) is empty.",
                                    pixel_type(),filename?filename:"(null)",width,height,depth,dim,data);
      if (!filename)
        throw CImgArgumentException("CImg<%s>::save_graphicsmagick_external() : Instance image (%u,%u,%u,%u,%p), specified filename is (null).",
                                    pixel_type(),width,height,depth,dim,data);
      char command[1024],filetmp[512];
      cimg_std::FILE *file;
      do {
        if (dim==1) cimg_std::sprintf(filetmp,"%s%s%s.pgm",cimg::temporary_path(),cimg_OS==2?"\\":"/",cimg::filenamerand());
        else cimg_std::sprintf(filetmp,"%s%s%s.ppm",cimg::temporary_path(),cimg_OS==2?"\\":"/",cimg::filenamerand());
        if ((file=cimg_std::fopen(filetmp,"rb"))!=0) cimg_std::fclose(file);
      } while (file);
      save_pnm(filetmp);
      cimg_std::sprintf(command,"%s -quality %u%% %s \"%s\"",cimg::graphicsmagick_path(),quality,filetmp,filename);
      cimg::system(command);
      file = cimg_std::fopen(filename,"rb");
      if (!file)
        throw CImgIOException("CImg<%s>::save_graphicsmagick_external() : Failed to save image '%s'.\n\n"
                              "Path of 'gm' : \"%s\"\n"
                              "Path of temporary filename : \"%s\"\n",
                              pixel_type(),filename,cimg::graphicsmagick_path(),filetmp);
      if (file) cimg::fclose(file);
      cimg_std::remove(filetmp);
      return *this;
    }

    //! Save an image as a gzipped file, using external tool 'gzip'.
    const CImg<T>& save_gzip_external(const char *const filename) const {
      if (!filename)
        throw CImgIOException("CImg<%s>::save_gzip_external() : Cannot save (null) filename.",
                              pixel_type());
      char command[1024], filetmp[512], body[512];
      const char
        *ext = cimg::split_filename(filename,body),
        *ext2 = cimg::split_filename(body,0);
      cimg_std::FILE *file;
      do {
        if (!cimg::strcasecmp(ext,"gz")) {
          if (*ext2) cimg_std::sprintf(filetmp,"%s%s%s.%s",cimg::temporary_path(),cimg_OS==2?"\\":"/",
                                  cimg::filenamerand(),ext2);
          else cimg_std::sprintf(filetmp,"%s%s%s.cimg",cimg::temporary_path(),cimg_OS==2?"\\":"/",
                            cimg::filenamerand());
        } else {
          if (*ext) cimg_std::sprintf(filetmp,"%s%s%s.%s",cimg::temporary_path(),cimg_OS==2?"\\":"/",
                                 cimg::filenamerand(),ext);
          else cimg_std::sprintf(filetmp,"%s%s%s.cimg",cimg::temporary_path(),cimg_OS==2?"\\":"/",
                                 cimg::filenamerand());
        }
        if ((file=cimg_std::fopen(filetmp,"rb"))!=0) cimg_std::fclose(file);
      } while (file);
      save(filetmp);
      cimg_std::sprintf(command,"%s -c %s > \"%s\"",cimg::gzip_path(),filetmp,filename);
      cimg::system(command);
      file = cimg_std::fopen(filename,"rb");
      if (!file)
        throw CImgIOException("CImgList<%s>::save_gzip_external() : File '%s' cannot be saved.",
                              pixel_type(),filename);
      else cimg::fclose(file);
      cimg_std::remove(filetmp);
      return *this;
    }

    //! Save the image using ImageMagick's convert.
    /** Function that saves the image for other file formats that are not natively handled by CImg,
        using the tool 'convert' from the ImageMagick package.\n
        This is the case for all compressed image formats (GIF,PNG,JPG,TIF, ...). You need to install
        the ImageMagick package in order to get
        this function working properly (see http://www.imagemagick.org ).
    **/
    const CImg<T>& save_imagemagick_external(const char *const filename, const unsigned int quality=100) const {
      if (is_empty())
        throw CImgInstanceException("CImg<%s>::save_imagemagick_external() : File '%s', instance image (%u,%u,%u,%u,%p) is empty.",
                                    pixel_type(),filename?filename:"(null)",width,height,depth,dim,data);
      if (!filename)
        throw CImgArgumentException("CImg<%s>::save_imagemagick_external() : Instance image (%u,%u,%u,%u,%p), specified filename is (null).",
                                    pixel_type(),width,height,depth,dim,data);
      char command[1024], filetmp[512];
      cimg_std::FILE *file;
      do {
        cimg_std::sprintf(filetmp,"%s%s%s.%s",cimg::temporary_path(),cimg_OS==2?"\\":"/",cimg::filenamerand(),dim==1?"pgm":"ppm");
        if ((file=cimg_std::fopen(filetmp,"rb"))!=0) cimg_std::fclose(file);
      } while (file);
      save_pnm(filetmp);
      cimg_std::sprintf(command,"%s -quality %u%% %s \"%s\"",cimg::imagemagick_path(),quality,filetmp,filename);
      cimg::system(command);
      file = cimg_std::fopen(filename,"rb");
      if (!file)
        throw CImgIOException("CImg<%s>::save_imagemagick_external() : Failed to save image '%s'.\n\n"
                              "Path of 'convert' : \"%s\"\n"
                              "Path of temporary filename : \"%s\"\n",
                              pixel_type(),filename,cimg::imagemagick_path(),filetmp);
      if (file) cimg::fclose(file);
      cimg_std::remove(filetmp);
      return *this;
    }

    //! Save an image as a Dicom file (need '(X)Medcon' : http://xmedcon.sourceforge.net )
    const CImg<T>& save_medcon_external(const char *const filename) const {
      if (is_empty())
        throw CImgInstanceException("CImg<%s>::save_medcon_external() : File '%s', instance image (%u,%u,%u,%u,%p) is empty.",
                                    pixel_type(),filename?filename:"(null)",width,height,depth,dim,data);
      if (!filename)
        throw CImgArgumentException("CImg<%s>::save_medcon_external() : Instance image (%u,%u,%u,%u,%p), specified filename is (null).",
                                    pixel_type(),width,height,depth,dim,data);

      char command[1024], filetmp[512], body[512];
      cimg_std::FILE *file;
      do {
        cimg_std::sprintf(filetmp,"%s.hdr",cimg::filenamerand());
        if ((file=cimg_std::fopen(filetmp,"rb"))!=0) cimg_std::fclose(file);
      } while (file);
      save_analyze(filetmp);
      cimg_std::sprintf(command,"%s -w -c dicom -o %s -f %s",cimg::medcon_path(),filename,filetmp);
      cimg::system(command);
      cimg_std::remove(filetmp);
      cimg::split_filename(filetmp,body);
      cimg_std::sprintf(filetmp,"%s.img",body);
      cimg_std::remove(filetmp);
      cimg_std::sprintf(command,"m000-%s",filename);
      file = cimg_std::fopen(command,"rb");
      if (!file) {
        cimg::fclose(cimg::fopen(filename,"r"));
        throw CImgIOException("CImg<%s>::save_medcon_external() : Failed to save image '%s'.\n\n"
                              "Path of 'medcon' : \"%s\"\n"
                              "Path of temporary filename : \"%s\"",
                              pixel_type(),filename,cimg::medcon_path(),filetmp);
      } else cimg::fclose(file);
      cimg_std::rename(command,filename);
      return *this;
    }

    // Try to save the image if other extension is provided.
    const CImg<T>& save_other(const char *const filename, const unsigned int quality=100) const {
      if (is_empty())
        throw CImgInstanceException("CImg<%s>::save_other() : File '%s', instance image (%u,%u,%u,%u,%p) is empty.",
                                    pixel_type(),filename?filename:"(null)",width,height,depth,dim,data);
      if (!filename)
        throw CImgIOException("CImg<%s>::save_other() : Instance image (%u,%u,%u,%u,%p), specified filename is (null).",
                              pixel_type());
      const unsigned int odebug = cimg::exception_mode();
      bool is_saved = true;
      cimg::exception_mode() = 0;
      try { save_magick(filename); }
      catch (CImgException&) {
        try { save_imagemagick_external(filename,quality); }
        catch (CImgException&) {
          try { save_graphicsmagick_external(filename,quality); }
          catch (CImgException&) {
            is_saved = false;
          }
        }
      }
      cimg::exception_mode() = odebug;
      if (!is_saved)
        throw CImgIOException("CImg<%s>::save_other() : File '%s' cannot be saved.\n"
                              "Check you have either the ImageMagick or GraphicsMagick package installed.",
                              pixel_type(),filename);
      return *this;
    }

    // Get a 40x38 color logo of a 'danger' item (internal).
    static CImg<T> logo40x38() {
      static bool first_time = true;
      CImg<T> res(40,38,1,3);
      if (first_time) {
        const unsigned char *ptrs = cimg::logo40x38;
        T *ptr1 = res.ptr(0,0,0,0), *ptr2 = res.ptr(0,0,0,1), *ptr3 = res.ptr(0,0,0,2);
        for (unsigned int off = 0; off<res.width*res.height;) {
          const unsigned char n = *(ptrs++), r = *(ptrs++), g = *(ptrs++), b = *(ptrs++);
          for (unsigned int l=0; l<n; ++off, ++l) { *(ptr1++) = (T)r; *(ptr2++) = (T)g; *(ptr3++) = (T)b; }
        }
        first_time = false;
      }
      return res;
    }

  };

  /*
   #-----------------------------------------
   #
   #
   #
   # Definition of the CImgList<> structure
   #
   #
   #
   #------------------------------------------
   */

  //! Class representing list of images CImg<T>.
  template<typename T>
  struct CImgList {

    //! Size of the list (number of elements inside).
    unsigned int size;

    //! Allocation size of the list.
    unsigned int allocsize;

    //! Pointer to the first list element.
    CImg<T> *data;

    //! Define a CImgList<T>::iterator.
    typedef CImg<T>* iterator;

    //! Define a CImgList<T>::const_iterator.
    typedef const CImg<T>* const_iterator;

    //! Get value type.
    typedef T value_type;

    // Define common T-dependant types.
    typedef typename cimg::superset<T,bool>::type Tbool;
    typedef typename cimg::superset<T,unsigned char>::type Tuchar;
    typedef typename cimg::superset<T,char>::type Tchar;
    typedef typename cimg::superset<T,unsigned short>::type Tushort;
    typedef typename cimg::superset<T,short>::type Tshort;
    typedef typename cimg::superset<T,unsigned int>::type Tuint;
    typedef typename cimg::superset<T,int>::type Tint;
    typedef typename cimg::superset<T,unsigned long>::type Tulong;
    typedef typename cimg::superset<T,long>::type Tlong;
    typedef typename cimg::superset<T,float>::type Tfloat;
    typedef typename cimg::superset<T,double>::type Tdouble;
    typedef typename cimg::last<T,bool>::type boolT;
    typedef typename cimg::last<T,unsigned char>::type ucharT;
    typedef typename cimg::last<T,char>::type charT;
    typedef typename cimg::last<T,unsigned short>::type ushortT;
    typedef typename cimg::last<T,short>::type shortT;
    typedef typename cimg::last<T,unsigned int>::type uintT;
    typedef typename cimg::last<T,int>::type intT;
    typedef typename cimg::last<T,unsigned long>::type ulongT;
    typedef typename cimg::last<T,long>::type longT;
    typedef typename cimg::last<T,float>::type floatT;
    typedef typename cimg::last<T,double>::type doubleT;

    //@}
    //---------------------------
    //
    //! \name Plugins
    //@{
    //---------------------------
#ifdef cimglist_plugin
#include cimglist_plugin
#endif
#ifdef cimglist_plugin1
#include cimglist_plugin1
#endif
#ifdef cimglist_plugin2
#include cimglist_plugin2
#endif
#ifdef cimglist_plugin3
#include cimglist_plugin3
#endif
#ifdef cimglist_plugin4
#include cimglist_plugin4
#endif
#ifdef cimglist_plugin5
#include cimglist_plugin5
#endif
#ifdef cimglist_plugin6
#include cimglist_plugin6
#endif
#ifdef cimglist_plugin7
#include cimglist_plugin7
#endif
#ifdef cimglist_plugin8
#include cimglist_plugin8
#endif
    //@}

    //------------------------------------------
    //
    //! \name Constructors - Destructor - Copy
    //@{
    //------------------------------------------

    //! Destructor.
    ~CImgList() {
      if (data) delete[] data;
    }

    //! Default constructor.
    CImgList():
      size(0),allocsize(0),data(0) {}

    //! Construct an image list containing n empty images.
    explicit CImgList(const unsigned int n):
      size(n) {
      data = new CImg<T>[allocsize = cimg::max(16UL,cimg::nearest_pow2(n))];
    }

    //! Default copy constructor.
    template<typename t>
    CImgList(const CImgList<t>& list):
      size(0),allocsize(0),data(0) {
      assign(list.size);
      cimglist_for(*this,l) data[l].assign(list[l],false);
    }

    CImgList(const CImgList<T>& list):
      size(0),allocsize(0),data(0) {
      assign(list.size);
      cimglist_for(*this,l) data[l].assign(list[l],list[l].is_shared);
    }

    //! Advanced copy constructor.
    template<typename t>
    CImgList(const CImgList<t>& list, const bool shared):
      size(0),allocsize(0),data(0) {
      assign(list.size);
      if (shared)
        throw CImgArgumentException("CImgList<%s>::CImgList() : Cannot construct a list instance with shared images from "
                                    "a CImgList<%s> (different pixel types).",
                                    pixel_type(),CImgList<t>::pixel_type());
      cimglist_for(*this,l) data[l].assign(list[l],false);
    }

    CImgList(const CImgList<T>& list, const bool shared):
      size(0),allocsize(0),data(0) {
      assign(list.size);
      cimglist_for(*this,l) data[l].assign(list[l],shared);
    }

    //! Construct an image list containing n images with specified size.
    CImgList(const unsigned int n, const unsigned int width, const unsigned int height=1,
             const unsigned int depth=1, const unsigned int dim=1):
      size(0),allocsize(0),data(0) {
      assign(n);
      cimglist_for(*this,l) data[l].assign(width,height,depth,dim);
    }

    //! Construct an image list containing n images with specified size, filled with specified value.
    CImgList(const unsigned int n, const unsigned int width, const unsigned int height,
             const unsigned int depth, const unsigned int dim, const T val):
      size(0),allocsize(0),data(0) {
      assign(n);
      cimglist_for(*this,l) data[l].assign(width,height,depth,dim,val);
    }

    //! Construct an image list containing n images with specified size and specified pixel values (int version).
    CImgList(const unsigned int n, const unsigned int width, const unsigned int height,
             const unsigned int depth, const unsigned int dim, const int val0, const int val1, ...):
      size(0),allocsize(0),data(0) {
#define _CImgList_stdarg(t) { \
        assign(n,width,height,depth,dim); \
        const unsigned int siz = width*height*depth*dim, nsiz = siz*n; \
        T *ptrd = data->data; \
        va_list ap; \
        va_start(ap,val1); \
        for (unsigned int l=0, s=0, i=0; i<nsiz; ++i) { \
          *(ptrd++) = (T)(i==0?val0:(i==1?val1:va_arg(ap,t))); \
          if ((++s)==siz) { ptrd = data[++l].data; s=0; } \
        } \
        va_end(ap); \
      }
      _CImgList_stdarg(int);
    }

    //! Construct an image list containing n images with specified size and specified pixel values (double version).
    CImgList(const unsigned int n, const unsigned int width, const unsigned int height,
             const unsigned int depth, const unsigned int dim, const double val0, const double val1, ...):
      size(0),allocsize(0),data(0) {
      _CImgList_stdarg(double);
    }

    //! Construct a list containing n copies of the image img.
    template<typename t>
    CImgList(const unsigned int n, const CImg<t>& img):
      size(0),allocsize(0),data(0) {
      assign(n);
      cimglist_for(*this,l) data[l].assign(img,img.is_shared);
    }

    //! Construct a list containing n copies of the image img, forcing the shared state.
    template<typename t>
    CImgList(const unsigned int n, const CImg<t>& img, const bool shared):
      size(0),allocsize(0),data(0) {
      assign(n);
      cimglist_for(*this,l) data[l].assign(img,shared);
    }

    //! Construct an image list from one image.
    template<typename t>
    explicit CImgList(const CImg<t>& img):
      size(0),allocsize(0),data(0) {
      assign(1);
      data[0].assign(img,img.is_shared);
    }

    //! Construct an image list from one image, forcing the shared state.
    template<typename t>
    explicit CImgList(const CImg<t>& img, const bool shared):
      size(0),allocsize(0),data(0) {
      assign(1);
      data[0].assign(img,shared);
    }

    //! Construct an image list from two images.
    template<typename t1, typename t2>
    CImgList(const CImg<t1>& img1, const CImg<t2>& img2):
      size(0),allocsize(0),data(0) {
      assign(2);
      data[0].assign(img1,img1.is_shared); data[1].assign(img2,img2.is_shared);
    }

    //! Construct an image list from two images, forcing the shared state.
    template<typename t1, typename t2>
    CImgList(const CImg<t1>& img1, const CImg<t2>& img2, const bool shared):
      size(0),allocsize(0),data(0) {
      assign(2);
      data[0].assign(img1,shared); data[1].assign(img2,shared);
    }

    //! Construct an image list from three images.
    template<typename t1, typename t2, typename t3>
    CImgList(const CImg<t1>& img1, const CImg<t2>& img2, const CImg<t3>& img3):
      size(0),allocsize(0),data(0) {
      assign(3);
      data[0].assign(img1,img1.is_shared); data[1].assign(img2,img2.is_shared); data[2].assign(img3,img3.is_shared);
    }

    //! Construct an image list from three images, forcing the shared state.
    template<typename t1, typename t2, typename t3>
    CImgList(const CImg<t1>& img1, const CImg<t2>& img2, const CImg<t3>& img3, const bool shared):
      size(0),allocsize(0),data(0) {
      assign(3);
      data[0].assign(img1,shared); data[1].assign(img2,shared); data[2].assign(img3,shared);
    }

    //! Construct an image list from four images.
    template<typename t1, typename t2, typename t3, typename t4>
    CImgList(const CImg<t1>& img1, const CImg<t2>& img2, const CImg<t3>& img3, const CImg<t4>& img4):
      size(0),allocsize(0),data(0) {
      assign(4);
      data[0].assign(img1,img1.is_shared); data[1].assign(img2,img2.is_shared); data[2].assign(img3,img3.is_shared); data[3].assign(img4,img4.is_shared);
    }

    //! Construct an image list from four images, forcing the shared state.
    template<typename t1, typename t2, typename t3, typename t4>
    CImgList(const CImg<t1>& img1, const CImg<t2>& img2, const CImg<t3>& img3, const CImg<t4>& img4, const bool shared):
      size(0),allocsize(0),data(0) {
      assign(4);
      data[0].assign(img1,shared); data[1].assign(img2,shared); data[2].assign(img3,shared); data[3].assign(img4,shared);
    }

    //! Construct an image list from five images.
    template<typename t1, typename t2, typename t3, typename t4, typename t5>
    CImgList(const CImg<t1>& img1, const CImg<t2>& img2, const CImg<t3>& img3, const CImg<t4>& img4,
             const CImg<t5>& img5):
      size(0),allocsize(0),data(0) {
      assign(5);
      data[0].assign(img1,img1.is_shared); data[1].assign(img2,img2.is_shared); data[2].assign(img3,img3.is_shared); data[3].assign(img4,img4.is_shared);
      data[4].assign(img5,img5.is_shared);
    }

    //! Construct an image list from five images, forcing the shared state.
    template<typename t1, typename t2, typename t3, typename t4, typename t5>
    CImgList(const CImg<t1>& img1, const CImg<t2>& img2, const CImg<t3>& img3, const CImg<t4>& img4,
             const CImg<t5>& img5, const bool shared):
      size(0),allocsize(0),data(0) {
      assign(5);
      data[0].assign(img1,shared); data[1].assign(img2,shared); data[2].assign(img3,shared); data[3].assign(img4,shared);
      data[4].assign(img5,shared);
    }

    //! Construct an image list from six images.
    template<typename t1, typename t2, typename t3, typename t4, typename t5, typename t6>
    CImgList(const CImg<t1>& img1, const CImg<t2>& img2, const CImg<t3>& img3, const CImg<t4>& img4,
             const CImg<t5>& img5, const CImg<t6>& img6):
      size(0),allocsize(0),data(0) {
      assign(6);
      data[0].assign(img1,img1.is_shared); data[1].assign(img2,img2.is_shared); data[2].assign(img3,img3.is_shared); data[3].assign(img4,img4.is_shared);
      data[4].assign(img5,img5.is_shared); data[5].assign(img6,img6.is_shared);
    }

    //! Construct an image list from six images, forcing the shared state.
    template<typename t1, typename t2, typename t3, typename t4, typename t5, typename t6>
    CImgList(const CImg<t1>& img1, const CImg<t2>& img2, const CImg<t3>& img3, const CImg<t4>& img4,
             const CImg<t5>& img5, const CImg<t6>& img6, const bool shared):
      size(0),allocsize(0),data(0) {
      assign(6);
      data[0].assign(img1,shared); data[1].assign(img2,shared); data[2].assign(img3,shared); data[3].assign(img4,shared);
      data[4].assign(img5,shared); data[5].assign(img6,shared);
    }

    //! Construct an image list from seven images.
    template<typename t1, typename t2, typename t3, typename t4, typename t5, typename t6, typename t7>
    CImgList(const CImg<t1>& img1, const CImg<t2>& img2, const CImg<t3>& img3, const CImg<t4>& img4,
             const CImg<t5>& img5, const CImg<t6>& img6, const CImg<t7>& img7):
      size(0),allocsize(0),data(0) {
      assign(7);
      data[0].assign(img1,img1.is_shared); data[1].assign(img2,img2.is_shared); data[2].assign(img3,img3.is_shared); data[3].assign(img4,img4.is_shared);
      data[4].assign(img5,img5.is_shared); data[5].assign(img6,img6.is_shared); data[6].assign(img7,img7.is_shared);
    }

    //! Construct an image list from seven images, forcing the shared state.
    template<typename t1, typename t2, typename t3, typename t4, typename t5, typename t6, typename t7>
    CImgList(const CImg<t1>& img1, const CImg<t2>& img2, const CImg<t3>& img3, const CImg<t4>& img4,
             const CImg<t5>& img5, const CImg<t6>& img6, const CImg<t7>& img7, const bool shared):
      size(0),allocsize(0),data(0) {
      assign(7);
      data[0].assign(img1,shared); data[1].assign(img2,shared); data[2].assign(img3,shared); data[3].assign(img4,shared);
      data[4].assign(img5,shared); data[5].assign(img6,shared); data[6].assign(img7,shared);
    }

    //! Construct an image list from eight images.
    template<typename t1, typename t2, typename t3, typename t4, typename t5, typename t6, typename t7, typename t8>
    CImgList(const CImg<t1>& img1, const CImg<t2>& img2, const CImg<t3>& img3, const CImg<t4>& img4,
             const CImg<t5>& img5, const CImg<t6>& img6, const CImg<t7>& img7, const CImg<t8>& img8):
      size(0),allocsize(0),data(0) {
      assign(8);
      data[0].assign(img1,img1.is_shared); data[1].assign(img2,img2.is_shared); data[2].assign(img3,img3.is_shared); data[3].assign(img4,img4.is_shared);
      data[4].assign(img5,img5.is_shared); data[5].assign(img6,img6.is_shared); data[6].assign(img7,img7.is_shared); data[7].assign(img8,img8.is_shared);
    }

    //! Construct an image list from eight images, forcing the shared state.
    template<typename t1, typename t2, typename t3, typename t4, typename t5, typename t6, typename t7, typename t8>
    CImgList(const CImg<t1>& img1, const CImg<t2>& img2, const CImg<t3>& img3, const CImg<t4>& img4,
             const CImg<t5>& img5, const CImg<t6>& img6, const CImg<t7>& img7, const CImg<t8>& img8, const bool shared):
      size(0),allocsize(0),data(0) {
      assign(8);
      data[0].assign(img1,shared); data[1].assign(img2,shared); data[2].assign(img3,shared); data[3].assign(img4,shared);
      data[4].assign(img5,shared); data[5].assign(img6,shared); data[6].assign(img7,shared); data[7].assign(img8,shared);
    }

    //! Construct an image list from a filename.
    explicit CImgList(const char *const filename):
      size(0),allocsize(0),data(0) {
      assign(filename);
    }

    //! In-place version of the default constructor and default destructor.
    CImgList<T>& assign() {
      if (data) delete[] data;
      size = allocsize = 0;
      data = 0;
      return *this;
    }

    //! Equivalent to assign() (STL-compliant name).
    CImgList<T>& clear() {
      return assign();
    }

    //! In-place version of the corresponding constructor.
    CImgList<T>& assign(const unsigned int n) {
      if (n) {
        if (allocsize<n || allocsize>(n<<2)) {
          if (data) delete[] data;
          data = new CImg<T>[allocsize=cimg::max(16UL,cimg::nearest_pow2(n))];
        }
        size = n;
      } else assign();
      return *this;
    }

    //! In-place version of the corresponding constructor.
    CImgList<T>& assign(const unsigned int n, const unsigned int width, const unsigned int height=1,
                        const unsigned int depth=1, const unsigned int dim=1) {
      assign(n);
      cimglist_for(*this,l) data[l].assign(width,height,depth,dim);
      return *this;
    }

    //! In-place version of the corresponding constructor.
    CImgList<T>& assign(const unsigned int n, const unsigned int width, const unsigned int height,
                        const unsigned int depth, const unsigned int dim, const T val) {
      assign(n);
      cimglist_for(*this,l) data[l].assign(width,height,depth,dim,val);
      return *this;
    }

    //! In-place version of the corresponding constructor.
    CImgList<T>& assign(const unsigned int n, const unsigned int width, const unsigned int height,
                        const unsigned int depth, const unsigned int dim, const int val0, const int val1, ...) {
      _CImgList_stdarg(int);
      return *this;
    }

    //! In-place version of the corresponding constructor.
    CImgList<T>& assign(const unsigned int n, const unsigned int width, const unsigned int height,
                        const unsigned int depth, const unsigned int dim, const double val0, const double val1, ...) {
      _CImgList_stdarg(double);
      return *this;
    }

    //! In-place version of the copy constructor.
    template<typename t>
    CImgList<T>& assign(const CImgList<t>& list) {
      assign(list.size);
      cimglist_for(*this,l) data[l].assign(list[l],list[l].is_shared);
      return *this;
    }

    //! In-place version of the copy constructor.
    template<typename t>
    CImgList<T>& assign(const CImgList<t>& list, const bool shared) {
      assign(list.size);
      cimglist_for(*this,l) data[l].assign(list[l],shared);
      return *this;
    }

    //! In-place version of the corresponding constructor.
    template<typename t>
    CImgList<T>& assign(const unsigned int n, const CImg<t>& img, const bool shared=false) {
      assign(n);
      cimglist_for(*this,l) data[l].assign(img,shared);
      return *this;
    }

    //! In-place version of the corresponding constructor.
    template<typename t>
    CImgList<T>& assign(const CImg<t>& img, const bool shared=false) {
      assign(1);
      data[0].assign(img,shared);
      return *this;
    }

    //! In-place version of the corresponding constructor.
    template<typename t1, typename t2>
    CImgList<T>& assign(const CImg<t1>& img1, const CImg<t2>& img2, const bool shared=false) {
      assign(2);
      data[0].assign(img1,shared); data[1].assign(img2,shared);
      return *this;
    }

    //! In-place version of the corresponding constructor.
    template<typename t1, typename t2, typename t3>
    CImgList<T>& assign(const CImg<t1>& img1, const CImg<t2>& img2, const CImg<t3>& img3, const bool shared=false) {
      assign(3);
      data[0].assign(img1,shared); data[1].assign(img2,shared); data[2].assign(img3,shared);
      return *this;
    }

    //! In-place version of the corresponding constructor.
    template<typename t1, typename t2, typename t3, typename t4>
    CImgList<T>& assign(const CImg<t1>& img1, const CImg<t2>& img2, const CImg<t3>& img3, const CImg<t4>& img4,
                        const bool shared=false) {
      assign(4);
      data[0].assign(img1,shared); data[1].assign(img2,shared); data[2].assign(img3,shared); data[3].assign(img4,shared);
      return *this;
    }

    //! In-place version of the corresponding constructor.
    template<typename t1, typename t2, typename t3, typename t4, typename t5>
    CImgList<T>& assign(const CImg<t1>& img1, const CImg<t2>& img2, const CImg<t3>& img3, const CImg<t4>& img4,
                        const CImg<t5>& img5, const bool shared=false) {
      assign(5);
      data[0].assign(img1,shared); data[1].assign(img2,shared); data[2].assign(img3,shared); data[3].assign(img4,shared);
      data[4].assign(img5,shared);
      return *this;
    }

    //! In-place version of the corresponding constructor.
    template<typename t1, typename t2, typename t3, typename t4, typename t5, typename t6>
    CImgList<T>& assign(const CImg<t1>& img1, const CImg<t2>& img2, const CImg<t3>& img3, const CImg<t4>& img4,
                        const CImg<t5>& img5, const CImg<t6>& img6, const bool shared=false) {
      assign(6);
      data[0].assign(img1,shared); data[1].assign(img2,shared); data[2].assign(img3,shared); data[3].assign(img4,shared);
      data[4].assign(img5,shared); data[5].assign(img6,shared);
      return *this;
    }

    //! In-place version of the corresponding constructor.
    template<typename t1, typename t2, typename t3, typename t4, typename t5, typename t6, typename t7>
    CImgList<T>& assign(const CImg<t1>& img1, const CImg<t2>& img2, const CImg<t3>& img3, const CImg<t4>& img4,
                        const CImg<t5>& img5, const CImg<t6>& img6, const CImg<t7>& img7, const bool shared=false) {
      assign(7);
      data[0].assign(img1,shared); data[1].assign(img2,shared); data[2].assign(img3,shared); data[3].assign(img4,shared);
      data[4].assign(img5,shared); data[5].assign(img6,shared); data[6].assign(img7,shared);
      return *this;
    }

    //! In-place version of the corresponding constructor.
    template<typename t1, typename t2, typename t3, typename t4, typename t5, typename t6, typename t7, typename t8>
    CImgList<T>& assign(const CImg<t1>& img1, const CImg<t2>& img2, const CImg<t3>& img3, const CImg<t4>& img4,
                        const CImg<t5>& img5, const CImg<t6>& img6, const CImg<t7>& img7, const CImg<t8>& img8, const bool shared=false) {
      assign(8);
      data[0].assign(img1,shared); data[1].assign(img2,shared); data[2].assign(img3,shared); data[3].assign(img4,shared);
      data[4].assign(img5,shared); data[5].assign(img6,shared); data[6].assign(img7,shared); data[7].assign(img8,shared);
      return *this;
    }

    //! In-place version of the corresponding constructor.
    CImgList<T>& assign(const char *const filename) {
      return load(filename);
    }

    //! Transfer the content of the instance image list into another one.
    template<typename t>
    CImgList<T>& transfer_to(CImgList<t>& list) {
      list.assign(*this);
      assign();
      return list;
    }

    CImgList<T>& transfer_to(CImgList<T>& list) {
      list.assign();
      return swap(list);
    }

    //! Swap all fields of two CImgList instances (use with care !)
    CImgList<T>& swap(CImgList<T>& list) {
      cimg::swap(size,list.size);
      cimg::swap(allocsize,list.allocsize);
      cimg::swap(data,list.data);
      return list;
    }

    //! Return a string describing the type of the image pixels in the list (template parameter \p T).
    static const char* pixel_type() {
      return cimg::type<T>::string();
    }

    //! Return \p true if list is empty.
    bool is_empty() const {
      return (!data || !size);
    }

    //! Return \p true if list is not empty.
    operator bool() const {
      return !is_empty();
    }

    //! Return \p true if list if of specified size.
    bool is_sameN(const unsigned int n) const {
      return (size==n);
    }

    //! Return \p true if list if of specified size.
    template<typename t>
    bool is_sameN(const CImgList<t>& list) const {
      return (size==list.size);
    }

    // Define useful dimension check functions.
    // (not documented because they are macro-generated).
#define _cimglist_def_is_same1(axis) \
    bool is_same##axis(const unsigned int val) const { \
      bool res = true; for (unsigned int l = 0; l<size && res; ++l) res = data[l].is_same##axis(val); return res; \
    } \
    bool is_sameN##axis(const unsigned int n, const unsigned int val) const { \
      return is_sameN(n) && is_same##axis(val); \
    } \

#define _cimglist_def_is_same2(axis1,axis2) \
    bool is_same##axis1##axis2(const unsigned int val1, const unsigned int val2) const { \
      bool res = true; for (unsigned int l = 0; l<size && res; ++l) res = data[l].is_same##axis1##axis2(val1,val2); return res; \
    } \
    bool is_sameN##axis1##axis2(const unsigned int n, const unsigned int val1, const unsigned int val2) const { \
      return is_sameN(n) && is_same##axis1##axis2(val1,val2); \
    } \

#define _cimglist_def_is_same3(axis1,axis2,axis3) \
    bool is_same##axis1##axis2##axis3(const unsigned int val1, const unsigned int val2, const unsigned int val3) const { \
      bool res = true; for (unsigned int l = 0; l<size && res; ++l) res = data[l].is_same##axis1##axis2##axis3(val1,val2,val3); return res; \
    } \
    bool is_sameN##axis1##axis2##axis3(const unsigned int n, const unsigned int val1, const unsigned int val2, const unsigned int val3) const { \
      return is_sameN(n) && is_same##axis1##axis2##axis3(val1,val2,val3); \
    } \

#define _cimglist_def_is_same(axis) \
    template<typename t> bool is_same##axis(const CImg<t>& img) const { \
      bool res = true; for (unsigned int l = 0; l<size && res; ++l) res = data[l].is_same##axis(img); return res; \
    } \
    template<typename t> bool is_same##axis(const CImgList<t>& list) const { \
      const unsigned int lmin = cimg::min(size,list.size); \
      bool res = true; for (unsigned int l = 0; l<lmin && res; ++l) res = data[l].is_same##axis(list[l]); return res; \
    } \
    template<typename t> bool is_sameN##axis(const unsigned int n, const CImg<t>& img) const { \
      return (is_sameN(n) && is_same##axis(img)); \
    } \
    template<typename t> bool is_sameN##axis(const CImgList<t>& list) const { \
      return (is_sameN(list) && is_same##axis(list)); \
    }

    _cimglist_def_is_same(XY)
    _cimglist_def_is_same(XZ)
    _cimglist_def_is_same(XV)
    _cimglist_def_is_same(YZ)
    _cimglist_def_is_same(YV)
    _cimglist_def_is_same(XYZ)
    _cimglist_def_is_same(XYV)
    _cimglist_def_is_same(YZV)
    _cimglist_def_is_same(XYZV)
    _cimglist_def_is_same1(X)
    _cimglist_def_is_same1(Y)
    _cimglist_def_is_same1(Z)
    _cimglist_def_is_same1(V)
    _cimglist_def_is_same2(X,Y)
    _cimglist_def_is_same2(X,Z)
    _cimglist_def_is_same2(X,V)
    _cimglist_def_is_same2(Y,Z)
    _cimglist_def_is_same2(Y,V)
    _cimglist_def_is_same2(Z,V)
    _cimglist_def_is_same3(X,Y,Z)
    _cimglist_def_is_same3(X,Y,V)
    _cimglist_def_is_same3(X,Z,V)
    _cimglist_def_is_same3(Y,Z,V)

    bool is_sameXYZV(const unsigned int dx, const unsigned int dy, const unsigned int dz, const unsigned int dv) const {
      bool res = true;
      for (unsigned int l = 0; l<size && res; ++l) res = data[l].is_sameXYZV(dx,dy,dz,dv);
      return res;
    }

    bool is_sameNXYZV(const unsigned int n, const unsigned int dx, const unsigned int dy, const unsigned int dz, const unsigned int dv) const {
      return is_sameN(n) && is_sameXYZV(dx,dy,dz,dv);
    }

    //! Return \c true if the list contains the pixel (n,x,y,z,v).
    bool containsNXYZV(const int n, const int x=0, const int y=0, const int z=0, const int v=0) const {
      if (is_empty()) return false;
      return n>=0 && n<(int)size && x>=0 && x<data[n].dimx() && y>=0 && y<data[n].dimy() && z>=0 && z<data[n].dimz() && v>=0 && v<data[n].dimv();
    }

    //! Return \c true if the list contains the image (n).
    bool containsN(const int n) const {
      if (is_empty()) return false;
      return n>=0 && n<(int)size;
    }

    //! Return \c true if one of the image list contains the specified referenced value. If true, set coordinates (n,x,y,z,v).
    template<typename t>
    bool contains(const T& pixel, t& n, t& x, t&y, t& z, t& v) const {
      if (is_empty()) return false;
      cimglist_for(*this,l) if (data[l].contains(pixel,x,y,z,v)) { n = (t)l; return true; }
      return false;
    }

    //! Return \c true if one of the image list contains the specified referenced value. If true, set coordinates (n,x,y,z).
    template<typename t>
    bool contains(const T& pixel, t& n, t& x, t&y, t& z) const {
      t v;
      return contains(pixel,n,x,y,z,v);
    }

    //! Return \c true if one of the image list contains the specified referenced value. If true, set coordinates (n,x,y).
    template<typename t>
    bool contains(const T& pixel, t& n, t& x, t&y) const {
      t z,v;
      return contains(pixel,n,x,y,z,v);
    }

    //! Return \c true if one of the image list contains the specified referenced value. If true, set coordinates (n,x).
    template<typename t>
    bool contains(const T& pixel, t& n, t& x) const {
      t y,z,v;
      return contains(pixel,n,x,y,z,v);
    }

    //! Return \c true if one of the image list contains the specified referenced value. If true, set coordinates (n).
    template<typename t>
    bool contains(const T& pixel, t& n) const {
      t x,y,z,v;
      return contains(pixel,n,x,y,z,v);
    }

    //! Return \c true if one of the image list contains the specified referenced value.
    bool contains(const T& pixel) const {
      unsigned int n,x,y,z,v;
      return contains(pixel,n,x,y,z,v);
    }

    //! Return \c true if the list contains the image 'img'. If true, returns the position (n) of the image in the list.
    template<typename t>
    bool contains(const CImg<T>& img, t& n) const {
      if (is_empty()) return false;
      const CImg<T> *const ptr = &img;
      cimglist_for(*this,i) if (data+i==ptr) { n = (t)i; return true; }
      return false;
    }

    //! Return \c true if the list contains the image img.
    bool contains(const CImg<T>& img) const {
      unsigned int n;
      return contains(img,n);
    }

    //@}
    //------------------------------
    //
    //! \name Arithmetics Operators
    //@{
    //------------------------------

    //! Assignment operator
    template<typename t>
    CImgList<T>& operator=(const CImgList<t>& list) {
      return assign(list);
    }

    CImgList<T>& operator=(const CImgList<T>& list) {
      return assign(list);
    }

    //! Assignment operator.
    template<typename t>
    CImgList<T>& operator=(const CImg<t>& img) {
      cimglist_for(*this,l) data[l] = img;
      return *this;
    }

    //! Assignment operator.
    CImgList<T>& operator=(const T val) {
      cimglist_for(*this,l) data[l].fill(val);
      return *this;
    }

    //! Operator+.
    CImgList<T> operator+() const {
      return CImgList<T>(*this);
    }

    //! Operator+=.
#ifdef cimg_use_visualcpp6
    CImgList<T>& operator+=(const T val)
#else
    template<typename t>
    CImgList<T>& operator+=(const t val)
#endif
    {
      cimglist_for(*this,l) (*this)[l]+=val;
      return *this;
    }

    //! Operator+=.
    template<typename t>
    CImgList<T>& operator+=(const CImgList<t>& list) {
      const unsigned int sizemax = cimg::min(size,list.size);
      for (unsigned int l=0; l<sizemax; ++l) (*this)[l]+=list[l];
      return *this;
    }

    //! Operator++ (prefix).
    CImgList<T>& operator++() {
      cimglist_for(*this,l) ++(*this)[l];
      return *this;
    }

    //! Operator++ (postfix).
    CImgList<T> operator++(int) {
      CImgList<T> copy(*this);
      ++*this;
      return copy;
    }

    //! Operator-.
    CImgList<T> operator-() const {
      CImgList<T> res(size);
      cimglist_for(res,l) res[l].assign(-data[l]);
      return res;
    }

    //! Operator-=.
#ifdef cimg_use_visualcpp6
    CImgList<T>& operator-=(const T val)
#else
      template<typename t>
    CImgList<T>& operator-=(const t val)
#endif
    {
      cimglist_for(*this,l) (*this)[l]-=val;
      return *this;
    }

    //! Operator-=.
    template<typename t>
    CImgList<T>& operator-=(const CImgList<t>& list) {
      const unsigned int sizemax = min(size,list.size);
      for (unsigned int l=0; l<sizemax; ++l) (*this)[l]-=list[l];
      return *this;
    }

    //! Operator-- (prefix).
    CImgList<T>& operator--() {
      cimglist_for(*this,l) --(*this)[l];
      return *this;
    }

    //! Operator-- (postfix).
    CImgList<T> operator--(int) {
      CImgList<T> copy(*this);
      --*this;
      return copy;
    }

    //! Operator*=.
#ifdef cimg_use_visualcpp6
    CImgList<T>& operator*=(const double val)
#else
    template<typename t>
    CImgList<T>& operator*=(const t val)
#endif
    {
      cimglist_for(*this,l) (*this)[l]*=val;
      return *this;
    }

    //! Operator*=.
    template<typename t>
    CImgList<T>& operator*=(const CImgList<t>& list) {
      const unsigned int N = cimg::min(size,list.size);
      for (unsigned int l=0; l<N; ++l) (*this)[l]*=list[l];
      return this;
    }

    //! Operator/=.
#ifdef cimg_use_visualcpp6
    CImgList<T>& operator/=(const double val)
#else
    template<typename t>
    CImgList<T>& operator/=(const t val)
#endif
    {
      cimglist_for(*this,l) (*this)[l]/=val;
      return *this;
    }

    //! Operator/=.
    template<typename t>
    CImgList<T>& operator/=(const CImgList<t>& list) {
      const unsigned int N = cimg::min(size,list.size);
      for (unsigned int l=0; l<N; ++l) (*this)[l]/=list[l];
      return this;
    }

    //! Return a reference to the maximum pixel value of the instance list.
    const T& max() const {
      if (is_empty())
        throw CImgInstanceException("CImgList<%s>::max() : Instance image list is empty.",
                                    pixel_type());
      const T *ptrmax = data->data;
      T max_value = *ptrmax;
      cimglist_for(*this,l) {
        const CImg<T>& img = data[l];
        cimg_for(img,ptr,T) if ((*ptr)>max_value) max_value = *(ptrmax=ptr);
      }
      return *ptrmax;
    }

    //! Return a reference to the maximum pixel value of the instance list.
    T& max() {
      if (is_empty())
        throw CImgInstanceException("CImgList<%s>::max() : Instance image list is empty.",
                                    pixel_type());
      T *ptrmax = data->data;
      T max_value = *ptrmax;
      cimglist_for(*this,l) {
        const CImg<T>& img = data[l];
        cimg_for(img,ptr,T) if ((*ptr)>max_value) max_value = *(ptrmax=ptr);
      }
      return *ptrmax;
    }

    //! Return a reference to the minimum pixel value of the instance list.
    const T& min() const {
      if (is_empty())
        throw CImgInstanceException("CImgList<%s>::min() : Instance image list is empty.",
                                    pixel_type());
      const T *ptrmin = data->data;
      T min_value = *ptrmin;
      cimglist_for(*this,l) {
        const CImg<T>& img = data[l];
        cimg_for(img,ptr,T) if ((*ptr)<min_value) min_value = *(ptrmin=ptr);
      }
      return *ptrmin;
    }

    //! Return a reference to the minimum pixel value of the instance list.
    T& min() {
      if (is_empty())
        throw CImgInstanceException("CImgList<%s>::min() : Instance image list is empty.",
                                    pixel_type());
      T *ptrmin = data->data;
      T min_value = *ptrmin;
      cimglist_for(*this,l) {
        const CImg<T>& img = data[l];
        cimg_for(img,ptr,T) if ((*ptr)<min_value) min_value = *(ptrmin=ptr);
      }
      return *ptrmin;
    }

    //! Return a reference to the minimum pixel value of the instance list.
    template<typename t>
    const T& minmax(t& max_val) const {
      if (is_empty())
        throw CImgInstanceException("CImgList<%s>::minmax() : Instance image list is empty.",
                                    pixel_type());
      const T *ptrmin = data->data;
      T min_value = *ptrmin, max_value = min_value;
      cimglist_for(*this,l) {
        const CImg<T>& img = data[l];
        cimg_for(img,ptr,T) {
          const T val = *ptr;
          if (val<min_value) { min_value = val; ptrmin = ptr; }
          if (val>max_value) max_value = val;
        }
      }
      max_val = (t)max_value;
      return *ptrmin;
    }

    //! Return a reference to the minimum pixel value of the instance list.
    template<typename t>
    T& minmax(t& max_val) {
      if (is_empty())
        throw CImgInstanceException("CImgList<%s>::minmax() : Instance image list is empty.",
                                    pixel_type());
      T *ptrmin = data->data;
      T min_value = *ptrmin, max_value = min_value;
      cimglist_for(*this,l) {
        const CImg<T>& img = data[l];
        cimg_for(img,ptr,T) {
          const T val = *ptr;
          if (val<min_value) { min_value = val; ptrmin = ptr; }
          if (val>max_value) max_value = val;
        }
      }
      max_val = (t)max_value;
      return *ptrmin;
    }

    //! Return a reference to the minimum pixel value of the instance list.
    template<typename t>
    const T& maxmin(t& min_val) const {
      if (is_empty())
        throw CImgInstanceException("CImgList<%s>::maxmin() : Instance image list is empty.",
                                    pixel_type());
      const T *ptrmax = data->data;
      T min_value = *ptrmax, max_value = min_value;
      cimglist_for(*this,l) {
        const CImg<T>& img = data[l];
        cimg_for(img,ptr,T) {
          const T val = *ptr;
          if (val>max_value) { max_value = val; ptrmax = ptr; }
          if (val<min_value) min_value = val;
        }
      }
      min_val = (t)min_value;
      return *ptrmax;
    }

    //! Return a reference to the minimum pixel value of the instance list.
    template<typename t>
    T& maxmin(t& min_val) {
      if (is_empty())
        throw CImgInstanceException("CImgList<%s>::maxmin() : Instance image list is empty.",
                                    pixel_type());
      T *ptrmax = data->data;
      T min_value = *ptrmax, max_value = min_value;
      cimglist_for(*this,l) {
        const CImg<T>& img = data[l];
        cimg_for(img,ptr,T) {
          const T val = *ptr;
          if (val>max_value) { max_value = val; ptrmax = ptr; }
          if (val<min_value) min_value = val;
        }
      }
      min_val = (t)min_value;
      return *ptrmax;
    }

    //! Return the mean pixel value of the instance list.
    double mean() const {
      if (is_empty())
        throw CImgInstanceException("CImgList<%s>::mean() : Instance image list is empty.",
                                    pixel_type());
      double val = 0;
      unsigned int siz = 0;
      cimglist_for(*this,l) {
        const CImg<T>& img = data[l];
        cimg_for(img,ptr,T) val+=(double)*ptr;
        siz+=img.size();
      }
      return val/siz;
    }

    //! Return the variance of the instance list.
    double variance() {
      if (is_empty())
        throw CImgInstanceException("CImgList<%s>::variance() : Instance image list is empty.",
                                    pixel_type());
      double res = 0;
      unsigned int siz = 0;
      double S = 0, S2 = 0;
      cimglist_for(*this,l) {
        const CImg<T>& img = data[l];
        cimg_for(img,ptr,T) { const double val = (double)*ptr; S+=val;  S2+=val*val; }
        siz+=img.size();
      }
      res = (S2 - S*S/siz)/siz;
      return res;
    }

    //! Compute a list of statistics vectors (min,max,mean,variance,xmin,ymin,zmin,vmin,xmax,ymax,zmax,vmax).
    CImgList<T>& stats(const unsigned int variance_method=1) {
      if (is_empty()) return *this;
      cimglist_for(*this,l) data[l].stats(variance_method);
      return *this;
    }

    CImgList<Tfloat> get_stats(const unsigned int variance_method=1) const {
      CImgList<Tfloat> res(size);
      cimglist_for(*this,l) res[l] = data[l].get_stats(variance_method);
      return res;
    }

    //@}
    //-------------------------
    //
    //! \name List Manipulation
    //@{
    //-------------------------

    //! Return a reference to the i-th element of the image list.
    CImg<T>& operator[](const unsigned int pos) {
#if cimg_debug>=3
      if (pos>=size) {
        cimg::warn("CImgList<%s>::operator[] : bad list position %u, in a list of %u images",
                   pixel_type(),pos,size);
        return *data;
      }
#endif
      return data[pos];
    }

    const CImg<T>& operator[](const unsigned int pos) const {
#if cimg_debug>=3
      if (pos>=size) {
        cimg::warn("CImgList<%s>::operator[] : bad list position %u, in a list of %u images",
                   pixel_type(),pos,size);
        return *data;
      }
#endif
      return data[pos];
    }

    //! Equivalent to CImgList<T>::operator[]
    CImg<T>& operator()(const unsigned int pos) {
      return (*this)[pos];
    }

    const CImg<T>& operator()(const unsigned int pos) const {
      return (*this)[pos];
    }

    //! Return a reference to (x,y,z,v) pixel of the pos-th image of the list
    T& operator()(const unsigned int pos, const unsigned int x, const unsigned int y=0,
                  const unsigned int z=0, const unsigned int v=0) {
      return (*this)[pos](x,y,z,v);
    }
    const T& operator()(const unsigned int pos, const unsigned int x, const unsigned int y=0,
                        const unsigned int z=0, const unsigned int v=0) const {
      return (*this)[pos](x,y,z,v);
    }

    // This function is only here for template tricks.
    T _display_object3d_at2(const int i, const int j) const {
      return atNXY(i,0,j,0,0,0);
    }

    //! Read an image in specified position.
    CImg<T>& at(const int pos) {
      if (is_empty())
        throw CImgInstanceException("CImgList<%s>::at() : Instance list is empty.",
                                    pixel_type());
      return data[pos<0?0:pos>=(int)size?(int)size-1:pos];
    }

    //! Read a pixel value with Dirichlet boundary conditions.
    T& atNXYZV(const int pos, const int x, const int y, const int z, const int v, const T out_val) {
      return (pos<0 || pos>=(int)size)?(cimg::temporary(out_val)=out_val):data[pos].atXYZV(x,y,z,v,out_val);
    }

    T atNXYZV(const int pos, const int x, const int y, const int z, const int v, const T out_val) const {
      return (pos<0 || pos>=(int)size)?out_val:data[pos].atXYZV(x,y,z,v,out_val);
    }

    //! Read a pixel value with Neumann boundary conditions.
    T& atNXYZV(const int pos, const int x, const int y, const int z, const int v) {
      if (is_empty())
        throw CImgInstanceException("CImgList<%s>::atNXYZV() : Instance list is empty.",
                                    pixel_type());
      return _atNXYZV(pos,x,y,z,v);
    }

    T atNXYZV(const int pos, const int x, const int y, const int z, const int v) const {
      if (is_empty())
        throw CImgInstanceException("CImgList<%s>::atNXYZV() : Instance list is empty.",
                                    pixel_type());
      return _atNXYZV(pos,x,y,z,v);
    }

    T& _atNXYZV(const int pos, const int x, const int y, const int z, const int v) {
      return data[pos<0?0:(pos>=(int)size?(int)size-1:pos)].atXYZV(x,y,z,v);
    }

    T _atNXYZV(const int pos, const int x, const int y, const int z, const int v) const {
      return data[pos<0?0:(pos>=(int)size?(int)size-1:pos)].atXYZV(x,y,z,v);
    }

    //! Read a pixel value with Dirichlet boundary conditions for the four first coordinates (\c pos, \c x,\c y,\c z).
    T& atNXYZ(const int pos, const int x, const int y, const int z, const int v, const T out_val) {
      return (pos<0 || pos>=(int)size)?(cimg::temporary(out_val)=out_val):data[pos].atXYZ(x,y,z,v,out_val);
    }

    T atNXYZ(const int pos, const int x, const int y, const int z, const int v, const T out_val) const {
      return (pos<0 || pos>=(int)size)?out_val:data[pos].atXYZ(x,y,z,v,out_val);
    }

    //! Read a pixel value with Neumann boundary conditions for the four first coordinates (\c pos, \c x,\c y,\c z).
    T& atNXYZ(const int pos, const int x, const int y, const int z, const int v=0) {
      if (is_empty())
        throw CImgInstanceException("CImgList<%s>::atNXYZ() : Instance list is empty.",
                                    pixel_type());
      return _atNXYZ(pos,x,y,z,v);
    }

    T atNXYZ(const int pos, const int x, const int y, const int z, const int v=0) const {
      if (is_empty())
        throw CImgInstanceException("CImgList<%s>::atNXYZ() : Instance list is empty.",
                                    pixel_type());
      return _atNXYZ(pos,x,y,z,v);
    }

    T& _atNXYZ(const int pos, const int x, const int y, const int z, const int v=0) {
      return data[pos<0?0:(pos>=(int)size?(int)size-1:pos)].atXYZ(x,y,z,v);
    }

    T _atNXYZ(const int pos, const int x, const int y, const int z, const int v=0) const {
      return data[pos<0?0:(pos>=(int)size?(int)size-1:pos)].atXYZ(x,y,z,v);
    }

    //! Read a pixel value with Dirichlet boundary conditions for the three first coordinates (\c pos, \c x,\c y).
    T& atNXY(const int pos, const int x, const int y, const int z, const int v, const T out_val) {
      return (pos<0 || pos>=(int)size)?(cimg::temporary(out_val)=out_val):data[pos].atXY(x,y,z,v,out_val);
    }

    T atNXY(const int pos, const int x, const int y, const int z, const int v, const T out_val) const {
      return (pos<0 || pos>=(int)size)?out_val:data[pos].atXY(x,y,z,v,out_val);
    }

    //! Read a pixel value with Neumann boundary conditions for the three first coordinates (\c pos, \c x,\c y).
    T& atNXY(const int pos, const int x, const int y, const int z=0, const int v=0) {
      if (is_empty())
        throw CImgInstanceException("CImgList<%s>::atNXY() : Instance list is empty.",
                                    pixel_type());
      return _atNXY(pos,x,y,z,v);
    }

    T atNXY(const int pos, const int x, const int y, const int z=0, const int v=0) const {
      if (is_empty())
        throw CImgInstanceException("CImgList<%s>::atNXY() : Instance list is empty.",
                                    pixel_type());
      return _atNXY(pos,x,y,z,v);
    }

    T& _atNXY(const int pos, const int x, const int y, const int z=0, const int v=0) {
      return data[pos<0?0:(pos>=(int)size?(int)size-1:pos)].atXY(x,y,z,v);
    }

    T _atNXY(const int pos, const int x, const int y, const int z=0, const int v=0) const {
      return data[pos<0?0:(pos>=(int)size?(int)size-1:pos)].atXY(x,y,z,v);
    }

    //! Read a pixel value with Dirichlet boundary conditions for the two first coordinates (\c pos,\c x).
    T& atNX(const int pos, const int x, const int y, const int z, const int v, const T out_val) {
      return (pos<0 || pos>=(int)size)?(cimg::temporary(out_val)=out_val):data[pos].atX(x,y,z,v,out_val);
    }

    T atNX(const int pos, const int x, const int y, const int z, const int v, const T out_val) const {
      return (pos<0 || pos>=(int)size)?out_val:data[pos].atX(x,y,z,v,out_val);
    }

    //! Read a pixel value with Neumann boundary conditions for the two first coordinates (\c pos, \c x).
    T& atNX(const int pos, const int x, const int y=0, const int z=0, const int v=0) {
      if (is_empty())
        throw CImgInstanceException("CImgList<%s>::atNX() : Instance list is empty.",
                                    pixel_type());
      return _atNX(pos,x,y,z,v);
    }

    T atNX(const int pos, const int x, const int y=0, const int z=0, const int v=0) const {
      if (is_empty())
        throw CImgInstanceException("CImgList<%s>::atNX() : Instance list is empty.",
                                    pixel_type());
      return _atNX(pos,x,y,z,v);
    }

    T& _atNX(const int pos, const int x, const int y=0, const int z=0, const int v=0) {
      return data[pos<0?0:(pos>=(int)size?(int)size-1:pos)].atX(x,y,z,v);
    }

    T _atNX(const int pos, const int x, const int y=0, const int z=0, const int v=0) const {
      return data[pos<0?0:(pos>=(int)size?(int)size-1:pos)].atX(x,y,z,v);
    }

    //! Read a pixel value with Dirichlet boundary conditions for the first coordinates (\c pos).
    T& atN(const int pos, const int x, const int y, const int z, const int v, const T out_val) {
      return (pos<0 || pos>=(int)size)?(cimg::temporary(out_val)=out_val):(*this)(pos,x,y,z,v);
    }

    T atN(const int pos, const int x, const int y, const int z, const int v, const T out_val) const {
      return (pos<0 || pos>=(int)size)?out_val:(*this)(pos,x,y,z,v);
    }

    //! Read a pixel value with Neumann boundary conditions for the first coordinates (\c pos).
    T& atN(const int pos, const int x=0, const int y=0, const int z=0, const int v=0) {
      if (is_empty())
        throw CImgInstanceException("CImgList<%s>::atN() : Instance list is empty.",
                                    pixel_type());
      return _atN(pos,x,y,z,v);
    }

    T atN(const int pos, const int x=0, const int y=0, const int z=0, const int v=0) const {
      if (is_empty())
        throw CImgInstanceException("CImgList<%s>::atN() : Instance list is empty.",
                                    pixel_type());
      return _atN(pos,x,y,z,v);
    }

    T& _atN(const int pos, const int x=0, const int y=0, const int z=0, const int v=0) {
      return data[pos<0?0:(pos>=(int)size?(int)size-1:pos)](x,y,z,v);
    }

    T _atN(const int pos, const int x=0, const int y=0, const int z=0, const int v=0) const {
      return data[pos<0?0:(pos>=(int)size?(int)size-1:pos)](x,y,z,v);
    }

    //! Returns a reference to the last element.
    CImg<T>& back() {
      return (*this)(size-1);
    }

    const CImg<T>& back() const {
      return (*this)(size-1);
    }

    //! Returns a reference to the first element.
    CImg<T>& front() {
      return *data;
    }

    const CImg<T>& front() const {
      return *data;
    }

    //! Returns an iterator to the beginning of the vector.
    iterator begin() {
      return data;
    }

    const_iterator begin() const {
      return data;
    }

    //! Return a reference to the first image.
    const CImg<T>& first() const {
      return *data;
    }

    CImg<T>& first() {
      return *data;
    }

    //! Returns an iterator just past the last element.
    iterator end() {
      return data + size;
    }

    const_iterator end() const {
      return data + size;
    }

    //! Return a reference to the last image.
    const CImg<T>& last() const {
      return data[size - 1];
    }

    CImg<T>& last() {
      return data[size - 1];
    }

    //! Insert a copy of the image \p img into the current image list, at position \p pos.
    template<typename t>
    CImgList<T>& insert(const CImg<t>& img, const unsigned int pos, const bool shared) {
      const unsigned int npos = pos==~0U?size:pos;
      if (npos>size)
        throw CImgArgumentException("CImgList<%s>::insert() : Cannot insert at position %u into a list with %u elements",
                                    pixel_type(),npos,size);
      if (shared)
        throw CImgArgumentException("CImgList<%s>::insert(): Cannot insert a shared image CImg<%s> into a CImgList<%s>",
                                    pixel_type(),img.pixel_type(),pixel_type());
      CImg<T> *new_data = (++size>allocsize)?new CImg<T>[allocsize?(allocsize<<=1):(allocsize=16)]:0;
      if (!size || !data) {
        data = new_data;
        *data = img;
      } else {
        if (new_data) {
          if (npos) cimg_std::memcpy(new_data,data,sizeof(CImg<T>)*npos);
          if (npos!=size-1) cimg_std::memcpy(new_data+npos+1,data+npos,sizeof(CImg<T>)*(size-1-npos));
          cimg_std::memset(data,0,sizeof(CImg<T>)*(size-1));
          delete[] data;
          data = new_data;
        }
        else if (npos!=size-1) cimg_std::memmove(data+npos+1,data+npos,sizeof(CImg<T>)*(size-1-npos));
        data[npos].width = data[npos].height = data[npos].depth = data[npos].dim = 0; data[npos].data = 0;
        data[npos] = img;
      }
      return *this;
    }

    CImgList<T>& insert(const CImg<T>& img, const unsigned int pos, const bool shared) {
      const unsigned int npos = pos==~0U?size:pos;
      if (npos>size)
        throw CImgArgumentException("CImgList<%s>::insert() : Can't insert at position %u into a list with %u elements",
                                    pixel_type(),npos,size);
      if (&img>=data && &img<data+size) return insert(+img,pos,shared);
      CImg<T> *new_data = (++size>allocsize)?new CImg<T>[allocsize?(allocsize<<=1):(allocsize=16)]:0;
      if (!size || !data) {
        data = new_data;
        if (shared && img) {
          data->width = img.width; data->height = img.height; data->depth = img.depth; data->dim = img.dim;
          data->is_shared = true; data->data = img.data;
        } else *data = img;
      }
      else {
        if (new_data) {
          if (npos) cimg_std::memcpy(new_data,data,sizeof(CImg<T>)*npos);
          if (npos!=size-1) cimg_std::memcpy(new_data+npos+1,data+npos,sizeof(CImg<T>)*(size-1-npos));
          if (shared && img) {
            new_data[npos].width = img.width; new_data[npos].height = img.height; new_data[npos].depth = img.depth;
            new_data[npos].dim = img.dim; new_data[npos].is_shared = true; new_data[npos].data = img.data;
          } else {
            new_data[npos].width = new_data[npos].height = new_data[npos].depth = new_data[npos].dim = 0; new_data[npos].data = 0;
            new_data[npos] = img;
          }
          cimg_std::memset(data,0,sizeof(CImg<T>)*(size-1));
          delete[] data;
          data = new_data;
        } else {
          if (npos!=size-1) cimg_std::memmove(data+npos+1,data+npos,sizeof(CImg<T>)*(size-1-npos));
          if (shared && img) {
            data[npos].width = img.width; data[npos].height = img.height; data[npos].depth = img.depth; data[npos].dim = img.dim;
            data[npos].is_shared = true; data[npos].data = img.data;
          } else {
            data[npos].width = data[npos].height = data[npos].depth = data[npos].dim = 0; data[npos].data = 0;
            data[npos] = img;
          }
        }
      }
      return *this;
    }

    // The two functions below are necessary due to Visual C++ 6.0 function overloading bugs, when
    // default parameters are used in function signatures.
    template<typename t>
    CImgList<T>& insert(const CImg<t>& img, const unsigned int pos) {
      return insert(img,pos,false);
    }

    //! Insert a copy of the image \p img into the current image list, at position \p pos.
    template<typename t>
    CImgList<T>& insert(const CImg<t>& img) {
      return insert(img,~0U,false);
    }

    template<typename t>
    CImgList<T> get_insert(const CImg<t>& img, const unsigned int pos=~0U, const bool shared=false) const {
      return (+*this).insert(img,pos,shared);
    }

    //! Insert n empty images img into the current image list, at position \p pos.
    CImgList<T>& insert(const unsigned int n, const unsigned int pos=~0U) {
      CImg<T> foo;
      if (!n) return *this;
      const unsigned int npos = pos==~0U?size:pos;
      for (unsigned int i=0; i<n; ++i) insert(foo,npos+i);
      return *this;
    }

    CImgList<T> get_insert(const unsigned int n, const unsigned int pos=~0U) const {
      return (+*this).insert(n,pos);
    }

    //! Insert n copies of the image \p img into the current image list, at position \p pos.
    template<typename t>
    CImgList<T>& insert(const unsigned int n, const CImg<t>& img, const unsigned int pos=~0U, const bool shared=false) {
      if (!n) return *this;
      const unsigned int npos = pos==~0U?size:pos;
      insert(img,npos,shared);
      for (unsigned int i=1; i<n; ++i) insert(data[npos],npos+i,shared);
      return *this;
    }

    template<typename t>
    CImgList<T> get_insert(const unsigned int n, const CImg<t>& img, const unsigned int pos=~0U, const bool shared=false) const {
      return (+*this).insert(n,img,pos,shared);
    }

    //! Insert a copy of the image list \p list into the current image list, starting from position \p pos.
    template<typename t>
    CImgList<T>& insert(const CImgList<t>& list, const unsigned int pos=~0U, const bool shared=false) {
      const unsigned int npos = pos==~0U?size:pos;
      if ((void*)this!=(void*)&list) cimglist_for(list,l) insert(list[l],npos+l,shared);
      else insert(CImgList<T>(list),npos,shared);
      return *this;
    }

    template<typename t>
    CImgList<T> get_insert(const CImgList<t>& list, const unsigned int pos=~0U, const bool shared=false) const {
      return (+*this).insert(list,pos,shared);
    }

    //! Insert n copies of the list \p list at position \p pos of the current list.
    template<typename t>
    CImgList<T>& insert(const unsigned int n, const CImgList<t>& list, const unsigned int pos=~0U, const bool shared=false) {
      if (!n) return *this;
      const unsigned int npos = pos==~0U?size:pos;
      for (unsigned int i=0; i<n; ++i) insert(list,npos,shared);
      return *this;
    }

    template<typename t>
    CImgList<T> get_insert(const unsigned int n, const CImgList<t>& list, const unsigned int pos=~0U, const bool shared=false) const {
      return (+*this).insert(n,list,pos,shared);
    }

    //! Insert a copy of the image \p img at the end of the current image list.
    template<typename t>
    CImgList<T>& operator<<(const CImg<t>& img) {
      return insert(img);
    }

    //! Insert a copy of the image list \p list at the end of the current image list.
    template<typename t>
    CImgList<T>& operator<<(const CImgList<t>& list) {
      return insert(list);
    }

    //! Return a copy of the current image list, where the image \p img has been inserted at the end.
    template<typename t>
    CImgList<T>& operator>>(CImg<t>& img) const {
      typedef typename cimg::superset<T,t>::type Tt;
      return CImgList<Tt>(*this).insert(img);
    }

    //! Insert a copy of the current image list at the beginning of the image list \p list.
    template<typename t>
    CImgList<T>& operator>>(CImgList<t>& list) const {
      return list.insert(*this,0);
    }

    //! Remove the images at positions \p pos1 to \p pos2 from the image list.
    CImgList<T>& remove(const unsigned int pos1, const unsigned int pos2) {
      const unsigned int
        npos1 = pos1<pos2?pos1:pos2,
        tpos2 = pos1<pos2?pos2:pos1,
        npos2 = tpos2<size?tpos2:size-1;
      if (npos1>=size)
        cimg::warn("CImgList<%s>::remove() : Cannot remove images from a list (%p,%u), at positions %u->%u.",
                   pixel_type(),data,size,npos1,tpos2);
      else {
        if (tpos2>=size)
          cimg::warn("CImgList<%s>::remove() : Cannot remove all images from a list (%p,%u), at positions %u->%u.",
                     pixel_type(),data,size,npos1,tpos2);
        for (unsigned int k = npos1; k<=npos2; ++k) data[k].assign();
        const unsigned int nb = 1 + npos2 - npos1;
        if (!(size-=nb)) return assign();
        if (size>(allocsize>>2) || allocsize<=8) { // Removing items without reallocation.
          if (npos1!=size) cimg_std::memmove(data+npos1,data+npos2+1,sizeof(CImg<T>)*(size-npos1));
          cimg_std::memset(data+size,0,sizeof(CImg<T>)*nb);
        } else { // Removing items with reallocation.
          allocsize>>=2;
          while (allocsize>8 && size<(allocsize>>1)) allocsize>>=1;
          CImg<T> *new_data = new CImg<T>[allocsize];
          if (npos1) cimg_std::memcpy(new_data,data,sizeof(CImg<T>)*npos1);
          if (npos1!=size) cimg_std::memcpy(new_data+npos1,data+npos2+1,sizeof(CImg<T>)*(size-npos1));
          if (size!=allocsize) cimg_std::memset(new_data+size,0,sizeof(allocsize-size));
          cimg_std::memset(data,0,sizeof(CImg<T>)*(size+nb));
          delete[] data;
          data = new_data;
        }
      }
      return *this;
    }

    CImgList<T> get_remove(const unsigned int pos1, const unsigned int pos2) const {
      return (+*this).remove(pos1,pos2);
    }

    //! Remove the image at position \p pos from the image list.
    CImgList<T>& remove(const unsigned int pos) {
      return remove(pos,pos);
    }

    CImgList<T> get_remove(const unsigned int pos) const {
      return (+*this).remove(pos);
    }

    //! Remove the last image from the image list.
    CImgList<T>& remove() {
      if (size) return remove(size-1);
      else cimg::warn("CImgList<%s>::remove() : List is empty",
                      pixel_type());
      return *this;
    }

    CImgList<T> get_remove() const {
      return (+*this).remove();
    }

    //! Reverse list order.
    CImgList<T>& reverse() {
      for (unsigned int l=0; l<size/2; ++l) (*this)[l].swap((*this)[size-1-l]);
      return *this;
    }

    CImgList<T> get_reverse() const {
      return (+*this).reverse();
    }

    //! Get a sub-list.
    CImgList<T>& crop(const unsigned int i0, const unsigned int i1, const bool shared=false) {
      return get_crop(i0,i1,shared).transfer_to(*this);
    }

    CImgList<T> get_crop(const unsigned int i0, const unsigned int i1, const bool shared=false) const {
      if (i0>i1 || i1>=size)
        throw CImgArgumentException("CImgList<%s>::crop() : Cannot crop a sub-list (%u->%u) from a list (%u,%p)",
                                    pixel_type(),i0,i1,size,data);
      CImgList<T> res(i1-i0+1);
      cimglist_for(res,l) res[l].assign((*this)[i0+l],shared);
      return res;
    }

    //! Get sub-images of a sublist.
    CImgList<T>& crop(const unsigned int i0, const unsigned int i1,
                      const int x0, const int y0, const int z0, const int v0,
                      const int x1, const int y1, const int z1, const int v1) {
      return get_crop(i0,i1,x0,y0,z0,v0,x1,y1,z1,v1).transfer_to(*this);
    }

    CImgList<T> get_crop(const unsigned int i0, const unsigned int i1,
                         const int x0, const int y0, const int z0, const int v0,
                         const int x1, const int y1, const int z1, const int v1) const {
      if (i0>i1 || i1>=size)
        throw CImgArgumentException("CImgList<%s>::crop() : Cannot crop a sub-list (%u->%u) from a list (%u,%p)",
                                    pixel_type(),i0,i1,size,data);
      CImgList<T> res(i1-i0+1);
      cimglist_for(res,l) res[l] = (*this)[i0+l].get_crop(x0,y0,z0,v0,x1,y1,z1,v1);
      return res;
    }

    //! Get sub-images of a sublist.
    CImgList<T>& crop(const unsigned int i0, const unsigned int i1,
                      const int x0, const int y0, const int z0,
                      const int x1, const int y1, const int z1) {
      return get_crop(i0,i1,x0,y0,z0,x1,y1,z1).transfer_to(*this);
    }

    CImgList<T> get_crop(const unsigned int i0, const unsigned int i1,
                         const int x0, const int y0, const int z0,
                         const int x1, const int y1, const int z1) const {
      if (i0>i1 || i1>=size)
        throw CImgArgumentException("CImgList<%s>::crop() : Cannot crop a sub-list (%u->%u) from a list (%u,%p)",
                                    pixel_type(),i0,i1,size,data);
      CImgList<T> res(i1-i0+1);
      cimglist_for(res,l) res[l] = (*this)[i0+l].get_crop(x0,y0,z0,x1,y1,z1);
      return res;
    }

    //! Get sub-images of a sublist.
    CImgList<T>& crop(const unsigned int i0, const unsigned int i1,
                      const int x0, const int y0,
                      const int x1, const int y1) {
      return get_crop(i0,i1,x0,y0,x1,y1).transfer_to(*this);
    }

    CImgList<T> get_crop(const unsigned int i0, const unsigned int i1,
                         const int x0, const int y0,
                         const int x1, const int y1) const {
      if (i0>i1 || i1>=size)
        throw CImgArgumentException("CImgList<%s>::crop() : Cannot crop a sub-list (%u->%u) from a list (%u,%p)",
                                    pixel_type(),i0,i1,size,data);
      CImgList<T> res(i1-i0+1);
      cimglist_for(res,l) res[l] = (*this)[i0+l].get_crop(x0,y0,x1,y1);
      return res;
    }

    //! Get sub-images of a sublist.
    CImgList<T>& crop(const unsigned int i0, const unsigned int i1,
                      const int x0, const int x1) {
      return get_crop(i0,i1,x0,x1).transfer_to(*this);
    }

    CImgList<T> get_crop(const unsigned int i0, const unsigned int i1,
                         const int x0, const int x1) const {
      if (i0>i1 || i1>=size)
        throw CImgArgumentException("CImgList<%s>::crop() : Cannot crop a sub-list (%u->%u) from a list (%u,%p)",
                                    pixel_type(),i0,i1,size,data);
      CImgList<T> res(i1-i0+1);
      cimglist_for(res,l) res[l] = (*this)[i0+l].get_crop(x0,x1);
      return res;
    }

    //! Display an image list into a CImgDisplay.
    const CImgList<T>& operator>>(CImgDisplay& disp) const {
      return display(disp);
    }

    //! Insert image \p img at the end of the list.
    template<typename t>
    CImgList<T>& push_back(const CImg<t>& img) {
      return insert(img);
    }

    //! Insert image \p img at the front of the list.
    template<typename t>
    CImgList<T>& push_front(const CImg<t>& img) {
      return insert(img,0);
    }

    //! Insert list \p list at the end of the current list.
    template<typename t>
    CImgList<T>& push_back(const CImgList<t>& list) {
      return insert(list);
    }

    //! Insert list \p list at the front of the current list.
    template<typename t>
    CImgList<T>& push_front(const CImgList<t>& list) {
      return insert(list,0);
    }

    //! Remove last element of the list.
    CImgList<T>& pop_back() {
      return remove(size-1);
    }

    //! Remove first element of the list.
    CImgList<T>& pop_front() {
      return remove(0);
    }

    //! Remove the element pointed by iterator \p iter.
    CImgList<T>& erase(const iterator iter) {
      return remove(iter-data);
    }

    //@}
    //----------------------------
    //
    //! \name Fourier Transforms
    //@{
    //----------------------------

    //! Compute the Fast Fourier Transform (along the specified axis).
    CImgList<T>& FFT(const char axis, const bool invert=false) {
      if (is_empty())
        throw CImgInstanceException("CImgList<%s>::FFT() : Instance list (%u,%p) is empty",
                                    pixel_type(),size,data);
      if (!data[0])
        throw CImgInstanceException("CImgList<%s>::FFT() : Real part (%u,%u,%u,%u,%p) is empty",
                                                pixel_type(),data[0].width,data[0].height,data[0].depth,data[0].dim,data[0].data);
      if (size>2)
        cimg::warn("CImgList<%s>::FFT() : Instance list (%u,%p) have more than 2 images",
                   pixel_type(),size,data);
      if (size==1) insert(CImg<T>(data[0].width,data[0].height,data[0].depth,data[0].dim,0));
      CImg<T> &Ir = data[0], &Ii = data[1];
      if (Ir.width!=Ii.width || Ir.height!=Ii.height || Ir.depth!=Ii.depth || Ir.dim!=Ii.dim)
        throw CImgInstanceException("CImgList<%s>::FFT() : Real part (%u,%u,%u,%u,%p) and imaginary part (%u,%u,%u,%u,%p)"
                                    "have different dimensions",
                                    pixel_type(),Ir.width,Ir.height,Ir.depth,Ir.dim,Ir.data,Ii.width,Ii.height,Ii.depth,Ii.dim,Ii.data);

#ifdef cimg_use_fftw3
      fftw_complex *data_in;
      fftw_plan data_plan;

      switch (cimg::uncase(axis)) {
      case 'x' : {
        data_in = (fftw_complex*)fftw_malloc(sizeof(fftw_complex)*Ir.width);
        data_plan = fftw_plan_dft_1d(Ir.width,data_in,data_in,invert?FFTW_BACKWARD:FFTW_FORWARD,FFTW_ESTIMATE);
        cimg_forYZV(Ir,y,z,k) {
          T *ptrr = Ir.ptr(0,y,z,k), *ptri = Ii.ptr(0,y,z,k);
          double *ptrd = (double*)data_in;
          cimg_forX(Ir,x) { *(ptrd++) = (double)*(ptrr++); *(ptrd++) = (double)*(ptri++); }
          fftw_execute(data_plan);
          const unsigned int fact = Ir.width;
          if (invert) { cimg_forX(Ir,x) { *(--ptri) = (T)(*(--ptrd)/fact); *(--ptrr) = (T)(*(--ptrd)/fact); }}
          else { cimg_forX(Ir,x) { *(--ptri) = (T)*(--ptrd); *(--ptrr) = (T)*(--ptrd); }}
        }
      } break;

      case 'y' : {
        data_in = (fftw_complex*)fftw_malloc(sizeof(fftw_complex) * Ir.height);
        data_plan = fftw_plan_dft_1d(Ir.height,data_in,data_in,invert?FFTW_BACKWARD:FFTW_FORWARD,FFTW_ESTIMATE);
        const unsigned int off = Ir.width;
        cimg_forXZV(Ir,x,z,k) {
          T *ptrr = Ir.ptr(x,0,z,k), *ptri = Ii.ptr(x,0,z,k);
          double *ptrd = (double*)data_in;
          cimg_forY(Ir,y) { *(ptrd++) = (double)*ptrr; *(ptrd++) = (double)*ptri; ptrr+=off; ptri+=off; }
          fftw_execute(data_plan);
          const unsigned int fact = Ir.height;
          if (invert) { cimg_forY(Ir,y) { ptrr-=off; ptri-=off; *ptri = (T)(*(--ptrd)/fact); *ptrr = (T)(*(--ptrd)/fact); }}
          else { cimg_forY(Ir,y) { ptrr-=off; ptri-=off; *ptri = (T)*(--ptrd); *ptrr = (T)*(--ptrd); }}
        }
      } break;

      case 'z' : {
        data_in = (fftw_complex*)fftw_malloc(sizeof(fftw_complex) * Ir.depth);
        data_plan = fftw_plan_dft_1d(Ir.depth,data_in,data_in,invert?FFTW_BACKWARD:FFTW_FORWARD,FFTW_ESTIMATE);
        const unsigned int off = Ir.width*Ir.height;
        cimg_forXYV(Ir,x,y,k) {
          T *ptrr = Ir.ptr(x,y,0,k), *ptri = Ii.ptr(x,y,0,k);
          double *ptrd = (double*)data_in;
          cimg_forZ(Ir,z) { *(ptrd++) = (double)*ptrr; *(ptrd++) = (double)*ptri; ptrr+=off; ptri+=off; }
          fftw_execute(data_plan);
          const unsigned int fact = Ir.depth;
          if (invert) { cimg_forZ(Ir,z) { ptrr-=off; ptri-=off; *ptri = (T)(*(--ptrd)/fact); *ptrr = (T)(*(--ptrd)/fact); }}
          else { cimg_forZ(Ir,z) { ptrr-=off; ptri-=off; *ptri = (T)*(--ptrd); *ptrr = (T)*(--ptrd); }}
        }
      } break;

      case 'v' : {
        data_in = (fftw_complex*)fftw_malloc(sizeof(fftw_complex) * Ir.dim);
        data_plan = fftw_plan_dft_1d(Ir.dim,data_in,data_in,invert?FFTW_BACKWARD:FFTW_FORWARD,FFTW_ESTIMATE);
        const unsigned int off = Ir.width*Ir.height*Ir.depth;
        cimg_forXYZ(Ir,x,y,z) {
          T *ptrr = Ir.ptr(x,y,z,0), *ptri = Ii.ptr(x,y,z,0);
          double *ptrd = (double*)data_in;
          cimg_forV(Ir,k) { *(ptrd++) = (double)*ptrr; *(ptrd++) = (double)*ptri; ptrr+=off; ptri+=off; }
          fftw_execute(data_plan);
          const unsigned int fact = Ir.dim;
          if (invert) { cimg_forV(Ir,k) { ptrr-=off; ptri-=off; *ptri = (T)(*(--ptrd)/fact); *ptrr = (T)(*(--ptrd)/fact); }}
          else { cimg_forV(Ir,k) { ptrr-=off; ptri-=off; *ptri = (T)*(--ptrd); *ptrr = (T)*(--ptrd); }}
        }
      } break;
      }

      fftw_destroy_plan(data_plan);
      fftw_free(data_in);
#else
      switch (cimg::uncase(axis)) {
      case 'x' : { // Fourier along X
        const unsigned int N = Ir.width, N2 = (N>>1);
        if (((N-1)&N) && N!=1)
          throw CImgInstanceException("CImgList<%s>::FFT() : Dimension of instance image along 'x' is %d != 2^N",
                                      pixel_type(),N);
        for (unsigned int i=0, j=0; i<N2; ++i) {
          if (j>i) cimg_forYZV(Ir,y,z,v) { cimg::swap(Ir(i,y,z,v),Ir(j,y,z,v)); cimg::swap(Ii(i,y,z,v),Ii(j,y,z,v));
          if (j<N2) {
            const unsigned int ri = N-1-i, rj = N-1-j;
            cimg::swap(Ir(ri,y,z,v),Ir(rj,y,z,v)); cimg::swap(Ii(ri,y,z,v),Ii(rj,y,z,v));
          }}
          for (unsigned int m=N, n=N2; (j+=n)>=m; j-=m, m=n, n>>=1) {}
        }
        for (unsigned int delta=2; delta<=N; delta<<=1) {
          const unsigned int delta2 = (delta>>1);
          for (unsigned int i=0; i<N; i+=delta) {
            float wr = 1, wi = 0;
            const float angle = (float)((invert?+1:-1)*2*cimg::valuePI/delta),
                        ca = (float)cimg_std::cos(angle),
                        sa = (float)cimg_std::sin(angle);
            for (unsigned int k=0; k<delta2; ++k) {
              const unsigned int j = i + k, nj = j + delta2;
              cimg_forYZV(Ir,y,z,k) {
                T &ir = Ir(j,y,z,k), &ii = Ii(j,y,z,k), &nir = Ir(nj,y,z,k), &nii = Ii(nj,y,z,k);
                const float tmpr = (float)(wr*nir - wi*nii), tmpi = (float)(wr*nii + wi*nir);
                nir = (T)(ir - tmpr);
                nii = (T)(ii - tmpi);
                ir += (T)tmpr;
                ii += (T)tmpi;
              }
              const float nwr = wr*ca-wi*sa;
              wi = wi*ca + wr*sa;
              wr = nwr;
            }
          }
        }
        if (invert) (*this)/=N;
      } break;

      case 'y' : { // Fourier along Y
        const unsigned int N = Ir.height, N2 = (N>>1);
        if (((N-1)&N) && N!=1)
          throw CImgInstanceException("CImgList<%s>::FFT() : Dimension of instance image(s) along 'y' is %d != 2^N",
                                      pixel_type(),N);
        for (unsigned int i=0, j=0; i<N2; ++i) {
          if (j>i) cimg_forXZV(Ir,x,z,v) { cimg::swap(Ir(x,i,z,v),Ir(x,j,z,v)); cimg::swap(Ii(x,i,z,v),Ii(x,j,z,v));
          if (j<N2) {
            const unsigned int ri = N-1-i, rj = N-1-j;
            cimg::swap(Ir(x,ri,z,v),Ir(x,rj,z,v)); cimg::swap(Ii(x,ri,z,v),Ii(x,rj,z,v));
          }}
          for (unsigned int m=N, n=N2; (j+=n)>=m; j-=m, m=n, n>>=1) {}
        }
        for (unsigned int delta=2; delta<=N; delta<<=1) {
          const unsigned int delta2 = (delta>>1);
          for (unsigned int i=0; i<N; i+=delta) {
            float wr = 1, wi = 0;
            const float angle = (float)((invert?+1:-1)*2*cimg::valuePI/delta),
                        ca = (float)cimg_std::cos(angle), sa = (float)cimg_std::sin(angle);
            for (unsigned int k=0; k<delta2; ++k) {
              const unsigned int j = i + k, nj = j + delta2;
              cimg_forXZV(Ir,x,z,k) {
                T &ir = Ir(x,j,z,k), &ii = Ii(x,j,z,k), &nir = Ir(x,nj,z,k), &nii = Ii(x,nj,z,k);
                const float tmpr = (float)(wr*nir - wi*nii), tmpi = (float)(wr*nii + wi*nir);
                nir = (T)(ir - tmpr);
                nii = (T)(ii - tmpi);
                ir += (T)tmpr;
                ii += (T)tmpi;
              }
              const float nwr = wr*ca-wi*sa;
              wi = wi*ca + wr*sa;
              wr = nwr;
            }
          }
        }
        if (invert) (*this)/=N;
      } break;

      case 'z' : { // Fourier along Z
        const unsigned int N = Ir.depth, N2 = (N>>1);
        if (((N-1)&N) && N!=1)
          throw CImgInstanceException("CImgList<%s>::FFT() : Dimension of instance image(s) along 'z' is %d != 2^N",
                                      pixel_type(),N);
        for (unsigned int i=0, j=0; i<N2; ++i) {
          if (j>i) cimg_forXYV(Ir,x,y,v) { cimg::swap(Ir(x,y,i,v),Ir(x,y,j,v)); cimg::swap(Ii(x,y,i,v),Ii(x,y,j,v));
          if (j<N2) {
            const unsigned int ri = N-1-i, rj = N-1-j;
            cimg::swap(Ir(x,y,ri,v),Ir(x,y,rj,v)); cimg::swap(Ii(x,y,ri,v),Ii(x,y,rj,v));
          }}
          for (unsigned int m=N, n=N2; (j+=n)>=m; j-=m, m=n, n>>=1) {}
        }
        for (unsigned int delta=2; delta<=N; delta<<=1) {
          const unsigned int delta2 = (delta>>1);
          for (unsigned int i=0; i<N; i+=delta) {
            float wr = 1, wi = 0;
            const float angle = (float)((invert?+1:-1)*2*cimg::valuePI/delta),
                        ca = (float)cimg_std::cos(angle), sa = (float)cimg_std::sin(angle);
            for (unsigned int k=0; k<delta2; ++k) {
              const unsigned int j = i + k, nj = j + delta2;
              cimg_forXYV(Ir,x,y,k) {
                T &ir = Ir(x,y,j,k), &ii = Ii(x,y,j,k), &nir = Ir(x,y,nj,k), &nii = Ii(x,y,nj,k);
                const float tmpr = (float)(wr*nir - wi*nii), tmpi = (float)(wr*nii + wi*nir);
                nir = (T)(ir - tmpr);
                nii = (T)(ii - tmpi);
                ir += (T)tmpr;
                ii += (T)tmpi;
              }
              const float nwr = wr*ca-wi*sa;
              wi = wi*ca + wr*sa;
              wr = nwr;
            }
          }
        }
        if (invert) (*this)/=N;
      } break;

      default :
        throw CImgArgumentException("CImgList<%s>::FFT() : Invalid axis '%c', must be 'x','y' or 'z'.");
      }
#endif
      return *this;
    }

    CImgList<Tfloat> get_FFT(const char axis, const bool invert=false) const {
      return CImgList<Tfloat>(*this).FFT(axis,invert);
    }

    //! Compute the Fast Fourier Transform of a complex image.
    CImgList<T>& FFT(const bool invert=false) {
      if (is_empty())
        throw CImgInstanceException("CImgList<%s>::FFT() : Instance list (%u,%p) is empty",
                                    pixel_type(),size,data);
      if (size>2)
        cimg::warn("CImgList<%s>::FFT() : Instance list (%u,%p) have more than 2 images",
                   pixel_type(),size,data);
      if (size==1) insert(CImg<T>(data->width,data->height,data->depth,data->dim,0));
      CImg<T> &Ir = data[0], &Ii = data[1];
      if (Ii.width!=Ir.width || Ii.height!=Ir.height || Ii.depth!=Ir.depth || Ii.dim!=Ir.dim)
        throw CImgInstanceException("CImgList<%s>::FFT() : Real (%u,%u,%u,%u,%p) and Imaginary (%u,%u,%u,%u,%p) parts "
                                    "of the instance image have different dimensions",
                                    pixel_type(),Ir.width,Ir.height,Ir.depth,Ir.dim,Ir.data,
                                    Ii.width,Ii.height,Ii.depth,Ii.dim,Ii.data);
#ifdef cimg_use_fftw3
      fftw_complex *data_in = (fftw_complex*)fftw_malloc(sizeof(fftw_complex) * Ir.width*Ir.height*Ir.depth);
      fftw_plan data_plan;
      const unsigned int w = Ir.width, wh = w*Ir.height, whd = wh*Ir.depth;
      data_plan = fftw_plan_dft_3d(Ir.width,Ir.height,Ir.depth,data_in,data_in,invert?FFTW_BACKWARD:FFTW_FORWARD,FFTW_ESTIMATE);
      cimg_forV(Ir,k) {
        T *ptrr = Ir.ptr(0,0,0,k), *ptri = Ii.ptr(0,0,0,k);
        double *ptrd = (double*)data_in;
        for (unsigned int x = 0; x<Ir.width; ++x, ptrr-=wh-1, ptri-=wh-1)
          for (unsigned int y = 0; y<Ir.height; ++y, ptrr-=whd-w, ptri-=whd-w)
            for (unsigned int z = 0; z<Ir.depth; ++z, ptrr+=wh, ptri+=wh) {
              *(ptrd++) = (double)*ptrr; *(ptrd++) = (double)*ptri;
            }
        fftw_execute(data_plan);
        ptrd = (double*)data_in;
        ptrr = Ir.ptr(0,0,0,k);
        ptri = Ii.ptr(0,0,0,k);
        if (!invert) for (unsigned int x = 0; x<Ir.width; ++x, ptrr-=wh-1, ptri-=wh-1)
          for (unsigned int y = 0; y<Ir.height; ++y, ptrr-=whd-w, ptri-=whd-w)
            for (unsigned int z = 0; z<Ir.depth; ++z, ptrr+=wh, ptri+=wh) {
              *ptrr = (T)*(ptrd++); *ptri = (T)*(ptrd++);
            }
        else for (unsigned int x = 0; x<Ir.width; ++x, ptrr-=wh-1, ptri-=wh-1)
          for (unsigned int y = 0; y<Ir.height; ++y, ptrr-=whd-w, ptri-=whd-w)
            for (unsigned int z = 0; z<Ir.depth; ++z, ptrr+=wh, ptri+=wh) {
              *ptrr = (T)(*(ptrd++)/whd); *ptri = (T)(*(ptrd++)/whd);
            }
      }
      fftw_destroy_plan(data_plan);
      fftw_free(data_in);
#else
      if (Ir.depth>1)  FFT('z',invert);
      if (Ir.height>1) FFT('y',invert);
      if (Ir.width>1)  FFT('x',invert);
#endif
      return *this;
    }

    CImgList<Tfloat> get_FFT(const bool invert=false) const {
      return CImgList<Tfloat>(*this).FFT(invert);
    }

    // Return a list where each image has been split along the specified axis.
    CImgList<T>& split(const char axis) {
      return get_split(axis).transfer_to(*this);
    }

    CImgList<T> get_split(const char axis) const {
      CImgList<T> res;
      cimglist_for(*this,l) {
        CImgList<T> tmp = data[l].get_split(axis);
        const unsigned int pos = res.size;
        res.insert(tmp.size);
        cimglist_for(tmp,i) tmp[i].transfer_to(data[pos+i]);
      }
      return res;
    }

    //! Return a single image which is the concatenation of all images of the current CImgList instance.
    /**
       \param axis : specify the axis for image concatenation. Can be 'x','y','z' or 'v'.
       \param align : specify the alignment for image concatenation. Can be 'p' (top), 'c' (center) or 'n' (bottom).
       \return A CImg<T> image corresponding to the concatenation is returned.
    **/
    CImg<T> get_append(const char axis, const char align='p') const {
      if (is_empty()) return CImg<T>();
      if (size==1) return +((*this)[0]);
      unsigned int dx = 0, dy = 0, dz = 0, dv = 0, pos = 0;
      CImg<T> res;
      switch (cimg::uncase(axis)) {
      case 'x' : {
        switch (cimg::uncase(align)) {
        case 'x' : { dy = dz = dv = 1; cimglist_for(*this,l) dx+=(*this)[l].size(); } break;
        case 'y' : { dx = size; dz = dv = 1; cimglist_for(*this,l) dy = cimg::max(dy,(unsigned int)(*this)[l].size()); } break;
        case 'z' : { dx = size; dy = dv = 1; cimglist_for(*this,l) dz = cimg::max(dz,(unsigned int)(*this)[l].size()); } break;
        case 'v' : { dx = size; dy = dz = 1; cimglist_for(*this,l) dv = cimg::max(dz,(unsigned int)(*this)[l].size()); } break;
        default :
          cimglist_for(*this,l) {
            const CImg<T>& img = (*this)[l];
            dx += img.width;
            dy = cimg::max(dy,img.height);
            dz = cimg::max(dz,img.depth);
            dv = cimg::max(dv,img.dim);
          }
        }
        res.assign(dx,dy,dz,dv,0);
        switch (cimg::uncase(align)) {
        case 'x' : {
          cimglist_for(*this,l) {
            res.draw_image(pos,CImg<T>((*this)[l],true).unroll('x'));
            pos+=(*this)[l].size();
          }
        } break;
        case 'y' : {
          cimglist_for(*this,l) res.draw_image(pos++,CImg<T>((*this)[l],true).unroll('y'));
        } break;
        case 'z' : {
          cimglist_for(*this,l) res.draw_image(pos++,CImg<T>((*this)[l],true).unroll('z'));
        } break;
        case 'v' : {
          cimglist_for(*this,l) res.draw_image(pos++,CImg<T>((*this)[l],true).unroll('v'));
        } break;
        case 'p' : {
          cimglist_for(*this,l) { res.draw_image(pos,(*this)[l]); pos+=(*this)[l].width; }
        } break;
        case 'n' : {
          cimglist_for(*this,l) {
            res.draw_image(pos,dy-(*this)[l].height,dz-(*this)[l].depth,dv-(*this)[l].dim,(*this)[l]);
            pos+=(*this)[l].width;
          }
        } break;
        default : {
          cimglist_for(*this,l) {
            res.draw_image(pos,(dy-(*this)[l].height)/2,(dz-(*this)[l].depth)/2,(dv-(*this)[l].dim)/2,(*this)[l]);
            pos+=(*this)[l].width;
          }
        } break;
        }
      } break;

      case 'y' : {
        switch (cimg::uncase(align)) {
        case 'x' : { dy = size; dz = dv = 1; cimglist_for(*this,l) dx = cimg::max(dx,(unsigned int)(*this)[l].size()); } break;
        case 'y' : { dx = dz = dv = 1; cimglist_for(*this,l) dy+=(*this)[l].size(); } break;
        case 'z' : { dy = size; dx = dv = 1; cimglist_for(*this,l) dz = cimg::max(dz,(unsigned int)(*this)[l].size()); } break;
        case 'v' : { dy = size; dx = dz = 1; cimglist_for(*this,l) dv = cimg::max(dv,(unsigned int)(*this)[l].size()); } break;
        default :
          cimglist_for(*this,l) {
            const CImg<T>& img = (*this)[l];
            dx = cimg::max(dx,img.width);
            dy += img.height;
            dz = cimg::max(dz,img.depth);
            dv = cimg::max(dv,img.dim);
          }
        }
        res.assign(dx,dy,dz,dv,0);
        switch (cimg::uncase(align)) {
        case 'x' : {
          cimglist_for(*this,l) res.draw_image(0,++pos,CImg<T>((*this)[l],true).unroll('x'));
        } break;
        case 'y' : {
          cimglist_for(*this,l) {
            res.draw_image(0,pos,CImg<T>((*this)[l],true).unroll('y'));
            pos+=(*this)[l].size();
          }
        } break;
        case 'z' : {
          cimglist_for(*this,l) res.draw_image(0,pos++,CImg<T>((*this)[l],true).unroll('z'));
        } break;
        case 'v' : {
          cimglist_for(*this,l) res.draw_image(0,pos++,CImg<T>((*this)[l],true).unroll('v'));
        } break;
        case 'p' : {
          cimglist_for(*this,l) { res.draw_image(0,pos,(*this)[l]); pos+=(*this)[l].height; }
        } break;
        case 'n' : {
          cimglist_for(*this,l) {
            res.draw_image(dx-(*this)[l].width,pos,dz-(*this)[l].depth,dv-(*this)[l].dim,(*this)[l]);
            pos+=(*this)[l].height;
          }
        } break;
        default : {
          cimglist_for(*this,l) {
            res.draw_image((dx-(*this)[l].width)/2,pos,(dz-(*this)[l].depth)/2,(dv-(*this)[l].dim)/2,(*this)[l]);
          pos+=(*this)[l].height;
          }
        } break;
        }
      } break;

      case 'z' : {
        switch (cimg::uncase(align)) {
        case 'x' : { dz = size; dy = dv = 1; cimglist_for(*this,l) dx = cimg::max(dx,(unsigned int)(*this)[l].size()); } break;
        case 'y' : { dz = size; dx = dv = 1; cimglist_for(*this,l) dy = cimg::max(dz,(unsigned int)(*this)[l].size()); } break;
        case 'z' : { dx = dy = dv = 1; cimglist_for(*this,l) dz+=(*this)[l].size(); } break;
        case 'v' : { dz = size; dx = dz = 1; cimglist_for(*this,l) dv = cimg::max(dv,(unsigned int)(*this)[l].size()); } break;
        default :
          cimglist_for(*this,l) {
            const CImg<T>& img = (*this)[l];
            dx = cimg::max(dx,img.width);
            dy = cimg::max(dy,img.height);
            dz += img.depth;
            dv = cimg::max(dv,img.dim);
          }
        }
        res.assign(dx,dy,dz,dv,0);
        switch (cimg::uncase(align)) {
        case 'x' : {
          cimglist_for(*this,l) res.draw_image(0,0,pos++,CImg<T>((*this)[l],true).unroll('x'));
        } break;
        case 'y' : {
          cimglist_for(*this,l) res.draw_image(0,0,pos++,CImg<T>((*this)[l],true).unroll('y'));
        } break;
        case 'z' : {
          cimglist_for(*this,l) {
            res.draw_image(0,0,pos,CImg<T>((*this)[l],true).unroll('z'));
            pos+=(*this)[l].size();
          }
        } break;
        case 'v' : {
          cimglist_for(*this,l) res.draw_image(0,0,pos++,CImg<T>((*this)[l],true).unroll('v'));
        } break;
        case 'p' : {
          cimglist_for(*this,l) { res.draw_image(0,0,pos,(*this)[l]); pos+=(*this)[l].depth; }
        } break;
        case 'n' : {
          cimglist_for(*this,l) {
            res.draw_image(dx-(*this)[l].width,dy-(*this)[l].height,pos,dv-(*this)[l].dim,(*this)[l]);
            pos+=(*this)[l].depth;
          }
        } break;
        case 'c' : {
          cimglist_for(*this,l) {
            res.draw_image((dx-(*this)[l].width)/2,(dy-(*this)[l].height)/2,pos,(dv-(*this)[l].dim)/2,(*this)[l]);
            pos+=(*this)[l].depth;
          }
        } break;
        }
      } break;

      case 'v' : {
        switch (cimg::uncase(align)) {
        case 'x' : { dv = size; dy = dv = 1; cimglist_for(*this,l) dx = cimg::max(dx,(unsigned int)(*this)[l].size()); } break;
        case 'y' : { dv = size; dx = dv = 1; cimglist_for(*this,l) dy = cimg::max(dz,(unsigned int)(*this)[l].size()); } break;
        case 'z' : { dv = size; dx = dv = 1; cimglist_for(*this,l) dz = cimg::max(dv,(unsigned int)(*this)[l].size()); } break;
        case 'v' : { dx = dy = dz = 1; cimglist_for(*this,l) dv+=(*this)[l].size(); } break;
        default :
          cimglist_for(*this,l) {
            const CImg<T>& img = (*this)[l];
            dx = cimg::max(dx,img.width);
            dy = cimg::max(dy,img.height);
            dz = cimg::max(dz,img.depth);
            dv += img.dim;
          }
        }
        res.assign(dx,dy,dz,dv,0);
        switch (cimg::uncase(align)) {
        case 'x' : {
          cimglist_for(*this,l) res.draw_image(0,0,0,pos++,CImg<T>((*this)[l],true).unroll('x'));
        } break;
        case 'y' : {
          cimglist_for(*this,l) res.draw_image(0,0,0,pos++,CImg<T>((*this)[l],true).unroll('y'));
        } break;
        case 'z' : {
          cimglist_for(*this,l) res.draw_image(0,0,0,pos++,CImg<T>((*this)[l],true).unroll('v'));
        } break;
        case 'v' : {
          cimglist_for(*this,l) {
            res.draw_image(0,0,0,pos,CImg<T>((*this)[l],true).unroll('z'));
            pos+=(*this)[l].size();
          }
        } break;
        case 'p' : {
          cimglist_for(*this,l) { res.draw_image(0,0,0,pos,(*this)[l]); pos+=(*this)[l].dim; }
        } break;
        case 'n' : {
          cimglist_for(*this,l) {
            res.draw_image(dx-(*this)[l].width,dy-(*this)[l].height,dz-(*this)[l].depth,pos,(*this)[l]);
            pos+=(*this)[l].dim;
          }
        } break;
        case 'c' : {
          cimglist_for(*this,l) {
            res.draw_image((dx-(*this)[l].width)/2,(dy-(*this)[l].height)/2,(dz-(*this)[l].depth)/2,pos,(*this)[l]);
            pos+=(*this)[l].dim;
          }
        } break;
        }
      } break;
      default :
        throw CImgArgumentException("CImgList<%s>::get_append() : unknow axis '%c', must be 'x','y','z' or 'v'",
                                    pixel_type(),axis);
      }
      return res;
    }

    //! Create an auto-cropped font (along the X axis) from a input font \p font.
    CImgList<T>& crop_font() {
      return get_crop_font().transfer_to(*this);
    }

    CImgList<T> get_crop_font() const {
      CImgList<T> res;
      cimglist_for(*this,l) {
        const CImg<T>& letter = (*this)[l];
        int xmin = letter.width, xmax = 0;
        cimg_forXY(letter,x,y) if (letter(x,y)) { if (x<xmin) xmin=x; if (x>xmax) xmax=x; }
        if (xmin>xmax) res.insert(CImg<T>(letter.width,letter.height,1,letter.dim,0));
        else res.insert(letter.get_crop(xmin,0,xmax,letter.height-1));
      }
      res[' '].resize(res['f'].width);
      res[' '+256].resize(res['f'].width);
      return res;
    }

    //! Invert primitives orientation of a 3D object.
    CImgList<T>& invert_object3d() {
      cimglist_for(*this,l) {
        CImg<T>& p = data[l];
        const unsigned int siz = p.size();
        if (siz==2 || siz==3 || siz==6 || siz==9) cimg::swap(p[0],p[1]);
        else if (siz==4 || siz==12) cimg::swap(p[0],p[3],p[1],p[2]);
      }
      return *this;
    }

    CImgList<T> get_invert_object3d() const {
      return (+*this).invert_object3d();
    }

    //! Return a CImg pre-defined font with desired size.
    /**
       \param font_height = height of the desired font (can be 11,13,24,38 or 57)
       \param fixed_size = tell if the font has a fixed or variable width.
    **/
    static CImgList<T> font(const unsigned int font_width, const bool variable_size=true) {
      if (font_width<=11) {
        static CImgList<T> font7x11, nfont7x11;
        if (!variable_size && !font7x11)  font7x11 = _font(cimg::font7x11,7,11,1,0,false);
        if (variable_size  && !nfont7x11) nfont7x11 = _font(cimg::font7x11,7,11,1,0,true);
        return variable_size?nfont7x11:font7x11;
      }
      if (font_width<=13) {
        static CImgList<T> font10x13, nfont10x13;
        if (!variable_size && !font10x13)  font10x13 = _font(cimg::font10x13,10,13,1,0,false);
        if (variable_size  && !nfont10x13) nfont10x13 = _font(cimg::font10x13,10,13,1,0,true);
        return variable_size?nfont10x13:font10x13;
      }
      if (font_width<=17) {
        static CImgList<T> font8x17, nfont8x17;
        if (!variable_size && !font8x17)  font8x17 = _font(cimg::font8x17,8,17,1,0,false);
        if (variable_size  && !nfont8x17) nfont8x17 = _font(cimg::font8x17,8,17,1,0,true);
        return variable_size?nfont8x17:font8x17;
      }
      if (font_width<=19) {
        static CImgList<T> font10x19, nfont10x19;
        if (!variable_size && !font10x19)  font10x19 = _font(cimg::font10x19,10,19,2,0,false);
        if (variable_size  && !nfont10x19) nfont10x19 = _font(cimg::font10x19,10,19,2,0,true);
        return variable_size?nfont10x19:font10x19;
      }
      if (font_width<=24) {
        static CImgList<T> font12x24, nfont12x24;
        if (!variable_size && !font12x24)  font12x24 = _font(cimg::font12x24,12,24,2,0,false);
        if (variable_size  && !nfont12x24) nfont12x24 = _font(cimg::font12x24,12,24,2,0,true);
        return variable_size?nfont12x24:font12x24;
      }
      if (font_width<=32) {
        static CImgList<T> font16x32, nfont16x32;
        if (!variable_size && !font16x32)  font16x32 = _font(cimg::font16x32,16,32,2,0,false);
        if (variable_size  && !nfont16x32) nfont16x32 = _font(cimg::font16x32,16,32,2,0,true);
        return variable_size?nfont16x32:font16x32;
      }
      if (font_width<=38) {
        static CImgList<T> font19x38, nfont19x38;
        if (!variable_size && !font19x38)  font19x38 = _font(cimg::font19x38,19,38,3,0,false);
        if (variable_size  && !nfont19x38) nfont19x38 = _font(cimg::font19x38,19,38,3,0,true);
        return variable_size?nfont19x38:font19x38;
      }
      static CImgList<T> font29x57, nfont29x57;
      if (!variable_size && !font29x57)  font29x57 = _font(cimg::font29x57,29,57,5,0,false);
      if (variable_size  && !nfont29x57) nfont29x57 = _font(cimg::font29x57,29,57,5,0,true);
      return variable_size?nfont29x57:font29x57;
    }

    static CImgList<T> _font(const unsigned int *const font, const unsigned int w, const unsigned int h,
                             const unsigned int paddingx, const unsigned int paddingy, const bool variable_size=true) {
      CImgList<T> res = CImgList<T>(256,w,h,1,3).insert(CImgList<T>(256,w,h,1,1));
      const unsigned int *ptr = font;
      unsigned int m = 0, val = 0;
      for (unsigned int y=0; y<h; ++y)
        for (unsigned int x=0; x<256*w; ++x) {
          m>>=1; if (!m) { m = 0x80000000; val = *(ptr++); }
          CImg<T>& img = res[x/w], &mask = res[x/w+256];
          unsigned int xm = x%w;
          img(xm,y,0) = img(xm,y,1) = img(xm,y,2) = mask(xm,y,0) = (T)((val&m)?1:0);
        }
      if (variable_size) res.crop_font();
      if (paddingx || paddingy) cimglist_for(res,l) res[l].resize(res[l].dimx()+paddingx, res[l].dimy()+paddingy,1,-100,0);
      return res;
    }

    //! Display the current CImgList instance in an existing CImgDisplay window (by reference).
    /**
       This function displays the list images of the current CImgList instance into an existing CImgDisplay window.
       Images of the list are concatenated in a single temporarly image for visualization purposes.
       The function returns immediately.
       \param disp : reference to an existing CImgDisplay instance, where the current image list will be displayed.
       \param axis : specify the axis for image concatenation. Can be 'x','y','z' or 'v'.
       \param align : specify the alignment for image concatenation. Can be 'p' (top), 'c' (center) or 'n' (bottom).
       \return A reference to the current CImgList instance is returned.
    **/
    const CImgList<T>& display(CImgDisplay& disp, const char axis='x', const char align='p') const {
      get_append(axis,align).display(disp);
      return *this;
    }

    //! Display the current CImgList instance in a new display window.
    /**
       This function opens a new window with a specific title and displays the list images of the current CImgList instance into it.
       Images of the list are concatenated in a single temporarly image for visualization purposes.
       The function returns when a key is pressed or the display window is closed by the user.
       \param title : specify the title of the opening display window.
       \param axis : specify the axis for image concatenation. Can be 'x','y','z' or 'v'.
       \param align : specify the alignment for image concatenation. Can be 'p' (top), 'c' (center) or 'n' (bottom).
       \return A reference to the current CImgList instance is returned.
    **/
    const CImgList<T>& display(CImgDisplay &disp,
                               const bool display_info, const char axis='x', const char align='p') const {
      if (is_empty())
        throw CImgInstanceException("CImgList<%s>::display() : Instance list (%u,%u) is empty.",
                                    pixel_type(),size,data);
      const CImg<T> visu = get_append(axis,align);
      if (display_info) print(disp.title);
      visu.display(disp,false);
      return *this;
    }

    //! Display the current CImgList instance in a new display window.
    const CImgList<T>& display(const char *const title=0,
                               const bool display_info=true, const char axis='x', const char align='p') const {
      const CImg<T> visu = get_append(axis,align);
      char ntitle[64] = { 0 };
      if (!title) cimg_std::sprintf(ntitle,"CImgList<%s>",pixel_type());
      if (display_info) print(title?title:ntitle);
      visu.display(title?title:ntitle,false);
      return *this;
    }

    //@}
    //----------------------------------
    //
    //! \name Input-Output
    //@{
    //----------------------------------

    //! Return a C-string containing the values of all images in the instance list.
    CImg<charT> value_string(const char separator=',', const unsigned int max_size=0) const {
      if (is_empty()) return CImg<ucharT>(1,1,1,1,0);
      CImgList<charT> items;
      for (unsigned int l = 0; l<size-1; ++l) {
        CImg<charT> item = data[l].value_string(separator,0);
        item[item.size()-1] = separator;
        items.insert(item);
      }
      items.insert(data[size-1].value_string(separator,0));
      CImg<charT> res = items.get_append('x');
      if (max_size) { res.crop(0,max_size); res(max_size) = 0; }
      return res;
    }

    //! Print informations about the list on the standard output.
    const CImgList<T>& print(const char* title=0, const bool display_stats=true) const {
      unsigned long msiz = 0;
      cimglist_for(*this,l) msiz += data[l].size();
      msiz*=sizeof(T);
      const unsigned int mdisp = msiz<8*1024?0:(msiz<8*1024*1024?1:2);
      char ntitle[64] = { 0 };
      if (!title) cimg_std::sprintf(ntitle,"CImgList<%s>",pixel_type());
      cimg_std::fprintf(cimg_stdout,"%s: this = %p, size = %u [%lu %s], data = (CImg<%s>*)%p.\n",
                   title?title:ntitle,(void*)this,size,
                   mdisp==0?msiz:(mdisp==1?(msiz>>10):(msiz>>20)),
                   mdisp==0?"b":(mdisp==1?"Kb":"Mb"),
                   pixel_type(),(void*)data);
      char tmp[16] = { 0 };
      cimglist_for(*this,ll) {
        cimg_std::sprintf(tmp,"[%d]",ll);
        cimg_std::fprintf(cimg_stdout,"  ");
        data[ll].print(tmp,display_stats);
        if (ll==3 && size>8) { ll = size-5; cimg_std::fprintf(cimg_stdout,"  ...\n"); }
      }
      return *this;
    }

    //! Load an image list from a file.
    CImgList<T>& load(const char *const filename) {
      const char *ext = cimg::split_filename(filename);
      const unsigned int odebug = cimg::exception_mode();
      cimg::exception_mode() = 0;
      assign();
      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);
        if (!cimg::strcasecmp(ext,"cimg") ||
            !cimg::strcasecmp(ext,"cimgz") ||
            !ext[0]) load_cimg(filename);
        if (!cimg::strcasecmp(ext,"rec") ||
            !cimg::strcasecmp(ext,"par")) load_parrec(filename);
        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,"mkv") ||
            !cimg::strcasecmp(ext,"mpe") ||
            !cimg::strcasecmp(ext,"movie") ||
            !cimg::strcasecmp(ext,"ogm") ||
            !cimg::strcasecmp(ext,"qt") ||
            !cimg::strcasecmp(ext,"rm") ||
            !cimg::strcasecmp(ext,"vob") ||
            !cimg::strcasecmp(ext,"wmv") ||
            !cimg::strcasecmp(ext,"xvid") ||
            !cimg::strcasecmp(ext,"mpeg")) load_ffmpeg(filename);
        if (!cimg::strcasecmp(ext,"gz")) load_gzip_external(filename);
        if (is_empty()) throw CImgIOException("CImgList<%s>::load()",pixel_type());
      } catch (CImgIOException& e) {
        if (!cimg::strncasecmp(e.message,"cimg::fopen()",13)) {
          cimg::exception_mode() = odebug;
          throw CImgIOException("CImgList<%s>::load() : File '%s' cannot be opened.",pixel_type(),filename);
        } else try {
          assign(1);
          data->load(filename);
        } catch (CImgException&) {
          assign();
        }
      }
      cimg::exception_mode() = odebug;
      if (is_empty())
        throw CImgIOException("CImgList<%s>::load() : File '%s', format not recognized.",pixel_type(),filename);
      return *this;
    }

    static CImgList<T> get_load(const char *const filename) {
      return CImgList<T>().load(filename);
    }

    //! Load an image list from a .cimg file.
    CImgList<T>& load_cimg(const char *const filename) {
      return _load_cimg(0,filename);
    }

    static CImgList<T> get_load_cimg(const char *const filename) {
      return CImgList<T>().load_cimg(filename);
    }

    //! Load an image list from a .cimg file.
    CImgList<T>& load_cimg(cimg_std::FILE *const file) {
      return _load_cimg(file,0);
    }

    static CImgList<T> get_load_cimg(cimg_std::FILE *const file) {
      return CImgList<T>().load_cimg(file);
    }

    CImgList<T>& _load_cimg(cimg_std::FILE *const file, const char *const filename) {
#ifdef cimg_use_zlib
#define _cimgz_load_cimg_case(Tss) { \
   Bytef *const cbuf = new Bytef[csiz]; \
   cimg::fread(cbuf,csiz,nfile); \
   raw.assign(W,H,D,V); \
   unsigned long destlen = raw.size()*sizeof(T); \
   uncompress((Bytef*)raw.data,&destlen,cbuf,csiz); \
   delete[] cbuf; \
   const Tss *ptrs = raw.data; \
   for (unsigned int off = raw.size(); off; --off) *(ptrd++) = (T)*(ptrs++); \
}
#else
#define _cimgz_load_cimg_case(Tss) \
   throw CImgIOException("CImgList<%s>::load_cimg() : File '%s' contains compressed data, zlib must be used",\
                         pixel_type(),filename?filename:"(FILE*)");
#endif

#define _cimg_load_cimg_case(Ts,Tss) \
      if (!loaded && !cimg::strcasecmp(Ts,str_pixeltype)) { \
        for (unsigned int l = 0; l<N; ++l) { \
          j = 0; while ((i=cimg_std::fgetc(nfile))!='\n' && i>=0) tmp[j++] = (char)i; tmp[j] = '\0'; \
          W = H = D = V = 0; csiz = 0; \
          if ((err = cimg_std::sscanf(tmp,"%u %u %u %u #%u",&W,&H,&D,&V,&csiz))<4) \
            throw CImgIOException("CImgList<%s>::load_cimg() : File '%s', Image %u has an invalid size (%u,%u,%u,%u)\n", \
                                  pixel_type(),filename?filename:("(FILE*)"),W,H,D,V); \
          if (W*H*D*V>0) { \
            CImg<Tss> raw; \
            CImg<T> &img = data[l]; \
            img.assign(W,H,D,V); \
            T *ptrd = img.data; \
            if (err==5) _cimgz_load_cimg_case(Tss) \
            else for (int toread = (int)img.size(); toread>0; ) { \
              raw.assign(cimg::min(toread,cimg_iobuffer)); \
              cimg::fread(raw.data,raw.width,nfile); \
              if (endian!=cimg::endianness()) cimg::invert_endianness(raw.data,raw.width); \
              toread-=raw.width; \
              const Tss *ptrs = raw.data; \
              for (unsigned int off = raw.width; off; --off) *(ptrd++) = (T)*(ptrs++); \
            } \
          } \
        } \
        loaded = true; \
      }

      if (!filename && !file)
        throw CImgArgumentException("CImgList<%s>::load_cimg() : Cannot load (null) filename.",
                                    pixel_type());
      typedef unsigned char uchar;
      typedef unsigned short ushort;
      typedef unsigned int uint;
      typedef unsigned long ulong;
      const int cimg_iobuffer = 12*1024*1024;
      cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"rb");
      bool loaded = false, endian = cimg::endianness();
      char tmp[256], str_pixeltype[256], str_endian[256];
      unsigned int j, err, N = 0, W, H, D, V, csiz;
      int i;
      j = 0; while((i=cimg_std::fgetc(nfile))!='\n' && i!=EOF && j<256) tmp[j++] = (char)i; tmp[j] = '\0';
      err = cimg_std::sscanf(tmp,"%u%*c%255[A-Za-z_]%*c%255[sA-Za-z_ ]",&N,str_pixeltype,str_endian);
      if (err<2) {
        if (!file) cimg::fclose(nfile);
        throw CImgIOException("CImgList<%s>::load_cimg() : File '%s', Unknow CImg RAW header.",
                              pixel_type(),filename?filename:"(FILE*)");
      }
      if (!cimg::strncasecmp("little",str_endian,6)) endian = false;
      else if (!cimg::strncasecmp("big",str_endian,3)) endian = true;
      assign(N);
      _cimg_load_cimg_case("bool",bool);
      _cimg_load_cimg_case("unsigned_char",uchar);
      _cimg_load_cimg_case("uchar",uchar);
      _cimg_load_cimg_case("char",char);
      _cimg_load_cimg_case("unsigned_short",ushort);
      _cimg_load_cimg_case("ushort",ushort);
      _cimg_load_cimg_case("short",short);
      _cimg_load_cimg_case("unsigned_int",uint);
      _cimg_load_cimg_case("uint",uint);
      _cimg_load_cimg_case("int",int);
      _cimg_load_cimg_case("unsigned_long",ulong);
      _cimg_load_cimg_case("ulong",ulong);
      _cimg_load_cimg_case("long",long);
      _cimg_load_cimg_case("float",float);
      _cimg_load_cimg_case("double",double);
      if (!loaded) {
        if (!file) cimg::fclose(nfile);
        throw CImgIOException("CImgList<%s>::load_cimg() : File '%s', cannot read images of pixels coded as '%s'.",
                              pixel_type(),filename?filename:"(FILE*)",str_pixeltype);
      }
      if (!file) cimg::fclose(nfile);
      return *this;
    }

    //! Load a sub-image list from a non compressed .cimg file.
    CImgList<T>& 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 v0,
                           const unsigned int x1, const unsigned int y1, const unsigned int z1, const unsigned int v1) {
      return _load_cimg(0,filename,n0,n1,x0,y0,z0,v0,x1,y1,z1,v1);
    }

    static CImgList<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 v0,
                                     const unsigned int x1, const unsigned int y1, const unsigned int z1, const unsigned int v1) {
      return CImgList<T>().load_cimg(filename,n0,n1,x0,y0,z0,v0,x1,y1,z1,v1);
    }

    //! Load a sub-image list from a non compressed .cimg file.
    CImgList<T>& load_cimg(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 v0,
                           const unsigned int x1, const unsigned int y1, const unsigned int z1, const unsigned int v1) {
      return _load_cimg(file,0,n0,n1,x0,y0,z0,v0,x1,y1,z1,v1);
    }

    static CImgList<T> get_load_cimg(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 v0,
                                     const unsigned int x1, const unsigned int y1, const unsigned int z1, const unsigned int v1) {
      return CImgList<T>().load_cimg(file,n0,n1,x0,y0,z0,v0,x1,y1,z1,v1);
    }

    CImgList<T>& _load_cimg(cimg_std::FILE *const file, 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 v0,
                            const unsigned int x1, const unsigned int y1, const unsigned int z1, const unsigned int v1) {
#define _cimg_load_cimg_case2(Ts,Tss) \
      if (!loaded && !cimg::strcasecmp(Ts,str_pixeltype)) { \
        for (unsigned int l = 0; l<=nn1; ++l) { \
          j = 0; while ((i=cimg_std::fgetc(nfile))!='\n' && i>=0) tmp[j++] = (char)i; tmp[j] = '\0'; \
          W = H = D = V = 0; \
          if (cimg_std::sscanf(tmp,"%u %u %u %u",&W,&H,&D,&V)!=4) \
            throw CImgIOException("CImgList<%s>::load_cimg() : File '%s', Image %u has an invalid size (%u,%u,%u,%u)\n", \
                                  pixel_type(), filename?filename:("(FILE*)"), W, H, D, V); \
          if (W*H*D*V>0) { \
            if (l<n0 || x0>=W || y0>=H || z0>=D || v0>=D) cimg_std::fseek(nfile,W*H*D*V*sizeof(Tss),SEEK_CUR); \
            else { \
              const unsigned int \
                nx1 = x1>=W?W-1:x1, \
                ny1 = y1>=H?H-1:y1, \
                nz1 = z1>=D?D-1:z1, \
                nv1 = v1>=V?V-1:v1; \
              CImg<Tss> raw(1+nx1-x0); \
              CImg<T> &img = data[l-n0]; \
              img.assign(1+nx1-x0,1+ny1-y0,1+nz1-z0,1+nv1-v0); \
              T *ptrd = img.data; \
              const unsigned int skipvb = v0*W*H*D*sizeof(Tss); \
              if (skipvb) cimg_std::fseek(nfile,skipvb,SEEK_CUR); \
              for (unsigned int v=1+nv1-v0; v; --v) { \
                const unsigned int skipzb = z0*W*H*sizeof(Tss); \
                if (skipzb) cimg_std::fseek(nfile,skipzb,SEEK_CUR); \
                for (unsigned int z=1+nz1-z0; z; --z) { \
                  const unsigned int skipyb = y0*W*sizeof(Tss); \
                  if (skipyb) cimg_std::fseek(nfile,skipyb,SEEK_CUR); \
                  for (unsigned int y=1+ny1-y0; y; --y) { \
                    const unsigned int skipxb = x0*sizeof(Tss); \
                    if (skipxb) cimg_std::fseek(nfile,skipxb,SEEK_CUR); \
                    cimg::fread(raw.data,raw.width,nfile); \
                    if (endian!=cimg::endianness()) cimg::invert_endianness(raw.data,raw.width); \
                    const Tss *ptrs = raw.data; \
                    for (unsigned int off = raw.width; off; --off) *(ptrd++) = (T)*(ptrs++); \
                    const unsigned int skipxe = (W-1-nx1)*sizeof(Tss); \
                    if (skipxe) cimg_std::fseek(nfile,skipxe,SEEK_CUR); \
                  } \
                  const unsigned int skipye = (H-1-ny1)*W*sizeof(Tss); \
                  if (skipye) cimg_std::fseek(nfile,skipye,SEEK_CUR); \
                } \
                const unsigned int skipze = (D-1-nz1)*W*H*sizeof(Tss); \
                if (skipze) cimg_std::fseek(nfile,skipze,SEEK_CUR); \
              } \
              const unsigned int skipve = (V-1-nv1)*W*H*D*sizeof(Tss); \
              if (skipve) cimg_std::fseek(nfile,skipve,SEEK_CUR); \
            } \
          } \
        } \
        loaded = true; \
      }

      if (!filename && !file)
        throw CImgArgumentException("CImgList<%s>::load_cimg() : Cannot load (null) filename.",
                                    pixel_type());
      typedef unsigned char uchar;
      typedef unsigned short ushort;
      typedef unsigned int uint;
      typedef unsigned long ulong;
      if (n1<n0 || x1<x0 || y1<y0 || z1<z0 || v1<v0)
        throw CImgArgumentException("CImgList<%s>::load_cimg() : File '%s', Bad sub-region coordinates [%u->%u] "
                                    "(%u,%u,%u,%u)->(%u,%u,%u,%u).",
                                    pixel_type(),filename?filename:"(FILE*)",
                                    n0,n1,x0,y0,z0,v0,x1,y1,z1,v1);
      cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"rb");
      bool loaded = false, endian = cimg::endianness();
      char tmp[256], str_pixeltype[256], str_endian[256];
      unsigned int j, err, N, W, H, D, V;
      int i;
      j = 0; while((i=cimg_std::fgetc(nfile))!='\n' && i!=EOF && j<256) tmp[j++] = (char)i; tmp[j] = '\0';
      err = cimg_std::sscanf(tmp,"%u%*c%255[A-Za-z_]%*c%255[sA-Za-z_ ]",&N,str_pixeltype,str_endian);
      if (err<2) {
        if (!file) cimg::fclose(nfile);
        throw CImgIOException("CImgList<%s>::load_cimg() : File '%s', Unknow CImg RAW header.",
                              pixel_type(),filename?filename:"(FILE*)");
      }
      if (!cimg::strncasecmp("little",str_endian,6)) endian = false;
      else if (!cimg::strncasecmp("big",str_endian,3)) endian = true;
      const unsigned int nn1 = n1>=N?N-1:n1;
      assign(1+nn1-n0);
      _cimg_load_cimg_case2("bool",bool);
      _cimg_load_cimg_case2("unsigned_char",uchar);
      _cimg_load_cimg_case2("uchar",uchar);
      _cimg_load_cimg_case2("char",char);
      _cimg_load_cimg_case2("unsigned_short",ushort);
      _cimg_load_cimg_case2("ushort",ushort);
      _cimg_load_cimg_case2("short",short);
      _cimg_load_cimg_case2("unsigned_int",uint);
      _cimg_load_cimg_case2("uint",uint);
      _cimg_load_cimg_case2("int",int);
      _cimg_load_cimg_case2("unsigned_long",ulong);
      _cimg_load_cimg_case2("ulong",ulong);
      _cimg_load_cimg_case2("long",long);
      _cimg_load_cimg_case2("float",float);
      _cimg_load_cimg_case2("double",double);
      if (!loaded) {
        if (!file) cimg::fclose(nfile);
        throw CImgIOException("CImgList<%s>::load_cimg() : File '%s', cannot read images of pixels coded as '%s'.",
                              pixel_type(),filename?filename:"(FILE*)",str_pixeltype);
      }
      if (!file) cimg::fclose(nfile);
      return *this;
    }

    //! Load an image list from a PAR/REC (Philips) file.
    CImgList<T>& load_parrec(const char *const filename) {
      if (!filename)
        throw CImgArgumentException("CImgList<%s>::load_parrec() : Cannot load (null) filename.",
                                    pixel_type());
      char body[1024], filenamepar[1024], filenamerec[1024];
      const char *ext = cimg::split_filename(filename,body);
      if (!cimg::strcmp(ext,"par")) { cimg_std::strcpy(filenamepar,filename); cimg_std::sprintf(filenamerec,"%s.rec",body); }
      if (!cimg::strcmp(ext,"PAR")) { cimg_std::strcpy(filenamepar,filename); cimg_std::sprintf(filenamerec,"%s.REC",body); }
      if (!cimg::strcmp(ext,"rec")) { cimg_std::strcpy(filenamerec,filename); cimg_std::sprintf(filenamepar,"%s.par",body); }
      if (!cimg::strcmp(ext,"REC")) { cimg_std::strcpy(filenamerec,filename); cimg_std::sprintf(filenamepar,"%s.PAR",body); }
      cimg_std::FILE *file = cimg::fopen(filenamepar,"r");

      // Parse header file
      CImgList<floatT> st_slices;
      CImgList<uintT> st_global;
      int err;
      char line[256] = { 0 };
      do { err=cimg_std::fscanf(file,"%255[^\n]%*c",line); } while (err!=EOF && (line[0]=='#' || line[0]=='.'));
      do {
        unsigned int sn,sizex,sizey,pixsize;
        float rs,ri,ss;
        err = cimg_std::fscanf(file,"%u%*u%*u%*u%*u%*u%*u%u%*u%u%u%g%g%g%*[^\n]",&sn,&pixsize,&sizex,&sizey,&ri,&rs,&ss);
        if (err==7) {
          st_slices.insert(CImg<floatT>::vector((float)sn,(float)pixsize,(float)sizex,(float)sizey,
                                               ri,rs,ss,0));
          unsigned int i; for (i=0; i<st_global.size && sn<=st_global[i][2]; ++i) {}
          if (i==st_global.size) st_global.insert(CImg<uintT>::vector(sizex,sizey,sn));
          else {
            CImg<uintT> &vec = st_global[i];
            if (sizex>vec[0]) vec[0] = sizex;
            if (sizey>vec[1]) vec[1] = sizey;
            vec[2] = sn;
          }
          st_slices[st_slices.size-1][7] = (float)i;
        }
      } while (err==7);

      // Read data
      cimg_std::FILE *file2 = cimg::fopen(filenamerec,"rb");
      { cimglist_for(st_global,l) {
        const CImg<uintT>& vec = st_global[l];
        insert(CImg<T>(vec[0],vec[1],vec[2]));
      }}

      cimglist_for(st_slices,l) {
        const CImg<floatT>& vec = st_slices[l];
        const unsigned int
          sn = (unsigned int)vec[0]-1,
          pixsize = (unsigned int)vec[1],
          sizex = (unsigned int)vec[2],
          sizey = (unsigned int)vec[3],
          imn = (unsigned int)vec[7];
        const float ri = vec[4], rs = vec[5], ss = vec[6];
        switch (pixsize) {
        case 8 : {
          CImg<ucharT> buf(sizex,sizey);
          cimg::fread(buf.data,sizex*sizey,file2);
          if (cimg::endianness()) cimg::invert_endianness(buf.data,sizex*sizey);
          CImg<T>& img = (*this)[imn];
          cimg_forXY(img,x,y) img(x,y,sn) = (T)(( buf(x,y)*rs + ri )/(rs*ss));
        } break;
        case 16 : {
          CImg<ushortT> buf(sizex,sizey);
          cimg::fread(buf.data,sizex*sizey,file2);
          if (cimg::endianness()) cimg::invert_endianness(buf.data,sizex*sizey);
          CImg<T>& img = (*this)[imn];
          cimg_forXY(img,x,y) img(x,y,sn) = (T)(( buf(x,y)*rs + ri )/(rs*ss));
        } break;
        case 32 : {
          CImg<uintT> buf(sizex,sizey);
          cimg::fread(buf.data,sizex*sizey,file2);
          if (cimg::endianness()) cimg::invert_endianness(buf.data,sizex*sizey);
          CImg<T>& img = (*this)[imn];
          cimg_forXY(img,x,y) img(x,y,sn) = (T)(( buf(x,y)*rs + ri )/(rs*ss));
        } break;
        default :
          cimg::fclose(file);
          cimg::fclose(file2);
          throw CImgIOException("CImg<%s>::load_parrec() : File '%s', cannot handle image with pixsize = %d bits.",
                                pixel_type(),filename,pixsize);
        }
      }
      cimg::fclose(file);
      cimg::fclose(file2);
      if (!size)
        throw CImgIOException("CImg<%s>::load_parrec() : File '%s' does not appear to be a valid PAR-REC file.",
                              pixel_type(),filename);
      return *this;
    }

    static CImgList<T> get_load_parrec(const char *const filename) {
      return CImgList<T>().load_parrec(filename);
    }

    //! Load an image sequence from a YUV file.
    CImgList<T>& load_yuv(const char *const filename,
                          const unsigned int sizex, const unsigned int sizey,
                          const unsigned int first_frame=0, const unsigned int last_frame=~0U,
                          const unsigned int step_frame=1, const bool yuv2rgb=true) {
      return _load_yuv(0,filename,sizex,sizey,first_frame,last_frame,step_frame,yuv2rgb);
    }

    static CImgList<T> get_load_yuv(const char *const filename,
                                    const unsigned int sizex, const unsigned int sizey=1,
                                    const unsigned int first_frame=0, const unsigned int last_frame=~0U,
                                    const unsigned int step_frame=1, const bool yuv2rgb=true) {
      return CImgList<T>().load_yuv(filename,sizex,sizey,first_frame,last_frame,step_frame,yuv2rgb);
    }

    //! Load an image sequence from a YUV file.
    CImgList<T>& load_yuv(cimg_std::FILE *const file,
                          const unsigned int sizex, const unsigned int sizey,
                          const unsigned int first_frame=0, const unsigned int last_frame=~0U,
                          const unsigned int step_frame=1, const bool yuv2rgb=true) {
      return _load_yuv(file,0,sizex,sizey,first_frame,last_frame,step_frame,yuv2rgb);
    }

    static CImgList<T> get_load_yuv(cimg_std::FILE *const file,
                                    const unsigned int sizex, const unsigned int sizey=1,
                                    const unsigned int first_frame=0, const unsigned int last_frame=~0U,
                                    const unsigned int step_frame=1, const bool yuv2rgb=true) {
      return CImgList<T>().load_yuv(file,sizex,sizey,first_frame,last_frame,step_frame,yuv2rgb);
    }

    CImgList<T>& _load_yuv(cimg_std::FILE *const file, const char *const filename,
                           const unsigned int sizex, const unsigned int sizey,
                           const unsigned int first_frame, const unsigned int last_frame,
                           const unsigned int step_frame, const bool yuv2rgb) {
      if (!filename && !file)
        throw CImgArgumentException("CImgList<%s>::load_yuv() : Cannot load (null) filename.",
                                    pixel_type());
      if (sizex%2 || sizey%2)
        throw CImgArgumentException("CImgList<%s>::load_yuv() : File '%s', image dimensions along X and Y must be "
                                    "even numbers (given are %ux%u)\n",
                                    pixel_type(),filename?filename:"(FILE*)",sizex,sizey);
      if (!sizex || !sizey)
        throw CImgArgumentException("CImgList<%s>::load_yuv() : File '%s', given image sequence size (%u,%u) is invalid",
                                    pixel_type(),filename?filename:"(FILE*)",sizex,sizey);

      const unsigned int
        nfirst_frame = first_frame<last_frame?first_frame:last_frame,
        nlast_frame = first_frame<last_frame?last_frame:first_frame,
        nstep_frame = step_frame?step_frame:1;

      CImg<ucharT> tmp(sizex,sizey,1,3), UV(sizex/2,sizey/2,1,2);
      cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"rb");
      bool stopflag = false;
      int err;
      if (nfirst_frame) {
        err = cimg_std::fseek(nfile,nfirst_frame*(sizex*sizey + sizex*sizey/2),SEEK_CUR);
        if (err) {
          if (!file) cimg::fclose(nfile);
          throw CImgIOException("CImgList<%s>::load_yuv() : File '%s' doesn't contain frame number %u "
                                "(out of range error).",
                                pixel_type(),filename?filename:"(FILE*)",nfirst_frame);
        }
      }
      unsigned int frame;
      for (frame = nfirst_frame; !stopflag && frame<=nlast_frame; frame+=nstep_frame) {
        tmp.fill(0);
        // *TRY* to read the luminance part, do not replace by cimg::fread !
        err = (int)cimg_std::fread((void*)(tmp.data),1,(size_t)(tmp.width*tmp.height),nfile);
        if (err!=(int)(tmp.width*tmp.height)) {
          stopflag = true;
          if (err>0)
            cimg::warn("CImgList<%s>::load_yuv() : File '%s' contains incomplete data,"
                       " or given image dimensions (%u,%u) are incorrect.",
                       pixel_type(),filename?filename:"(FILE*)",sizex,sizey);
        } else {
          UV.fill(0);
          // *TRY* to read the luminance part, do not replace by cimg::fread !
          err = (int)cimg_std::fread((void*)(UV.data),1,(size_t)(UV.size()),nfile);
          if (err!=(int)(UV.size())) {
            stopflag = true;
            if (err>0)
              cimg::warn("CImgList<%s>::load_yuv() : File '%s' contains incomplete data,"
                         " or given image dimensions (%u,%u) are incorrect.",
                         pixel_type(),filename?filename:"(FILE*)",sizex,sizey);
          } else {
            cimg_forXY(UV,x,y) {
              const int x2 = x*2, y2 = y*2;
              tmp(x2,y2,1) = tmp(x2+1,y2,1) = tmp(x2,y2+1,1) = tmp(x2+1,y2+1,1) = UV(x,y,0);
              tmp(x2,y2,2) = tmp(x2+1,y2,2) = tmp(x2,y2+1,2) = tmp(x2+1,y2+1,2) = UV(x,y,1);
            }
            if (yuv2rgb) tmp.YCbCrtoRGB();
            insert(tmp);
            if (nstep_frame>1) cimg_std::fseek(nfile,(nstep_frame-1)*(sizex*sizey + sizex*sizey/2),SEEK_CUR);
          }
        }
      }
      if (stopflag && nlast_frame!=~0U && frame!=nlast_frame)
        cimg::warn("CImgList<%s>::load_yuv() : File '%s', frame %d not reached since only %u frames were found in the file.",
                   pixel_type(),filename?filename:"(FILE*)",nlast_frame,frame-1,filename);
      if (!file) cimg::fclose(nfile);
      return *this;
    }

    //! Load an image from a video file, using ffmpeg libraries.
    // This piece of code has been firstly created by David Starweather (starkdg(at)users(dot)sourceforge(dot)net)
    // I modified it afterwards for direct inclusion in the library core.
    CImgList<T>& load_ffmpeg(const char *const filename, const unsigned int first_frame=0, const unsigned int last_frame=~0U,
                             const unsigned int step_frame=1, const bool pixel_format=true, const bool resume=false) {
      if (!filename)
        throw CImgArgumentException("CImgList<%s>::load_ffmpeg() : Cannot load (null) filename.",
                                    pixel_type());
      const unsigned int
        nfirst_frame = first_frame<last_frame?first_frame:last_frame,
        nlast_frame = first_frame<last_frame?last_frame:first_frame,
        nstep_frame = step_frame?step_frame:1;
      assign();

#ifndef cimg_use_ffmpeg
      if ((nfirst_frame || nlast_frame!=~0U || nstep_frame>1) || (resume && (pixel_format || !pixel_format)))
        throw CImgArgumentException("CImg<%s>::load_ffmpeg() : File '%s', reading sub-frames from a video file requires the use of ffmpeg.\n"
                                    "('cimg_use_ffmpeg' must be defined).",
                                    pixel_type(),filename);
      return load_ffmpeg_external(filename);
#else
      const unsigned int ffmpeg_pixfmt = pixel_format?PIX_FMT_RGB24:PIX_FMT_GRAY8;
      avcodec_register_all();
      av_register_all();
      static AVFormatContext *format_ctx = 0;
      static AVCodecContext *codec_ctx = 0;
      static AVCodec *codec = 0;
      static AVFrame *avframe = avcodec_alloc_frame(), *converted_frame = avcodec_alloc_frame();
      static int vstream = 0;

      if (resume) {
        if (!format_ctx || !codec_ctx || !codec || !avframe || !converted_frame)
          throw CImgArgumentException("CImgList<%s>::load_ffmpeg() : File '%s', cannot resume due to unallocated FFMPEG structures.",
                                      pixel_type(),filename);
      } else {
        // Open video file, find main video stream and codec.
        if (format_ctx) av_close_input_file(format_ctx);
        if (av_open_input_file(&format_ctx,filename,0,0,0)!=0)
          throw CImgIOException("CImgList<%s>::load_ffmpeg() : File '%s' cannot be opened.",
                                pixel_type(),filename);
        if (!avframe || !converted_frame || av_find_stream_info(format_ctx)<0) {
          av_close_input_file(format_ctx); format_ctx = 0;
          cimg::warn("CImgList<%s>::load_ffmpeg() : File '%s', cannot retrieve stream information.\n"
                     "Trying with external ffmpeg executable.",
                     pixel_type(),filename);
          return load_ffmpeg_external(filename);
        }
#if cimg_debug>=3
        dump_format(format_ctx,0,0,0);
#endif

        // Special command : Return informations on main video stream.
        // as a vector 1x4 containing : (nb_frames,width,height,fps).
        if (!first_frame && !last_frame && !step_frame) {
          for (vstream = 0; vstream<(int)(format_ctx->nb_streams); ++vstream)
            if (format_ctx->streams[vstream]->codec->codec_type==CODEC_TYPE_VIDEO) break;
          if (vstream==(int)format_ctx->nb_streams) assign();
          else {
            CImgList<doubleT> timestamps;
            int nb_frames;
            AVPacket packet;
            // Count frames and store timestamps.
            for (nb_frames = 0; av_read_frame(format_ctx,&packet)>=0; av_free_packet(&packet))
              if (packet.stream_index==vstream) {
                timestamps.insert(CImg<doubleT>::vector((double)packet.pts));
                ++nb_frames;
              }
            // Get frame with, height and fps.
            const int
              framew = format_ctx->streams[vstream]->codec->width,
              frameh = format_ctx->streams[vstream]->codec->height;
            const float
              num = (float)(format_ctx->streams[vstream]->r_frame_rate).num,
              den = (float)(format_ctx->streams[vstream]->r_frame_rate).den,
              fps = num/den;
            // Return infos as a list.
            assign(2);
            (*this)[0].assign(1,4).fill((T)nb_frames,(T)framew,(T)frameh,(T)fps);
            (*this)[1] = timestamps.get_append('y');
          }
          av_close_input_file(format_ctx); format_ctx = 0;
          return *this;
        }

        for (vstream = 0; vstream<(int)(format_ctx->nb_streams) &&
               format_ctx->streams[vstream]->codec->codec_type!=CODEC_TYPE_VIDEO; ) ++vstream;
        if (vstream==(int)format_ctx->nb_streams) {
          cimg::warn("CImgList<%s>::load_ffmpeg() : File '%s', cannot retrieve video stream.\n"
                     "Trying with external ffmpeg executable.",
                     pixel_type(),filename);
          av_close_input_file(format_ctx); format_ctx = 0;
          return load_ffmpeg_external(filename);
        }
        codec_ctx = format_ctx->streams[vstream]->codec;
        codec = avcodec_find_decoder(codec_ctx->codec_id);
        if (!codec) {
          cimg::warn("CImgList<%s>::load_ffmpeg() : File '%s', cannot find video codec.\n"
                     "Trying with external ffmpeg executable.",
                     pixel_type(),filename);
          return load_ffmpeg_external(filename);
        }
        if (avcodec_open(codec_ctx,codec)<0) { // Open codec
          cimg::warn("CImgList<%s>::load_ffmpeg() : File '%s', cannot open video codec.\n"
                     "Trying with external ffmpeg executable.",
                     pixel_type(),filename);
          return load_ffmpeg_external(filename);
        }
      }

      // Read video frames
      const unsigned int numBytes = avpicture_get_size(ffmpeg_pixfmt,codec_ctx->width,codec_ctx->height);
      uint8_t *const buffer = new uint8_t[numBytes];
      avpicture_fill((AVPicture *)converted_frame,buffer,ffmpeg_pixfmt,codec_ctx->width,codec_ctx->height);
      const T foo = (T)0;
      AVPacket packet;
      for (unsigned int frame = 0, next_frame = nfirst_frame; frame<=nlast_frame && av_read_frame(format_ctx,&packet)>=0; ) {
        if (packet.stream_index==(int)vstream) {
          int decoded = 0;
          avcodec_decode_video(codec_ctx,avframe,&decoded,packet.data,packet.size);
          if (decoded) {
            if (frame==next_frame) {
              SwsContext *c = sws_getContext(codec_ctx->width,codec_ctx->height,codec_ctx->pix_fmt,codec_ctx->width,
                                             codec_ctx->height,ffmpeg_pixfmt,1,0,0,0);
              sws_scale(c,avframe->data,avframe->linesize,0,codec_ctx->height,converted_frame->data,converted_frame->linesize);
              if (ffmpeg_pixfmt==PIX_FMT_RGB24) {
                CImg<ucharT> next_image(*converted_frame->data,3,codec_ctx->width,codec_ctx->height,1,true);
                insert(next_image._get_permute_axes("yzvx",foo));
              } else {
                CImg<ucharT> next_image(*converted_frame->data,1,codec_ctx->width,codec_ctx->height,1,true);
                insert(next_image._get_permute_axes("yzvx",foo));
              }
              next_frame+=nstep_frame;
            }
            ++frame;
          }
          av_free_packet(&packet);
          if (next_frame>nlast_frame) break;
        }
      }
      delete[] buffer;
#endif
      return *this;
    }

    static CImgList<T> get_load_ffmpeg(const char *const filename, const unsigned int first_frame=0, const unsigned int last_frame=~0U,
                                       const unsigned int step_frame=1, const bool pixel_format=true) {
      return CImgList<T>().load_ffmpeg(filename,first_frame,last_frame,step_frame,pixel_format);
    }

    //! Load an image from a video file (MPEG,AVI) using the external tool 'ffmpeg'.
    CImgList<T>& load_ffmpeg_external(const char *const filename) {
      if (!filename)
        throw CImgArgumentException("CImgList<%s>::load_ffmpeg_external() : Cannot load (null) filename.",
                                    pixel_type());
      char command[1024], filetmp[512], filetmp2[512];
      cimg_std::FILE *file = 0;
      do {
        cimg_std::sprintf(filetmp,"%s%s%s",cimg::temporary_path(),cimg_OS==2?"\\":"/",cimg::filenamerand());
        cimg_std::sprintf(filetmp2,"%s_000001.ppm",filetmp);
        if ((file=cimg_std::fopen(filetmp2,"rb"))!=0) cimg_std::fclose(file);
      } while (file);
      cimg_std::sprintf(filetmp2,"%s_%%6d.ppm",filetmp);
#if cimg_OS!=2
      cimg_std::sprintf(command,"%s -i \"%s\" %s >/dev/null 2>&1",cimg::ffmpeg_path(),filename,filetmp2);
#else
      cimg_std::sprintf(command,"\"%s -i \"%s\" %s\" >NUL 2>&1",cimg::ffmpeg_path(),filename,filetmp2);
#endif
      cimg::system(command,0);
      const unsigned int odebug = cimg::exception_mode();
      cimg::exception_mode() = 0;
      assign();
      unsigned int i = 1;
      for (bool stopflag = false; !stopflag; ++i) {
        cimg_std::sprintf(filetmp2,"%s_%.6u.ppm",filetmp,i);
        CImg<T> img;
        try { img.load_pnm(filetmp2); }
        catch (CImgException&) { stopflag = true; }
        if (img) { insert(img); cimg_std::remove(filetmp2); }
      }
      cimg::exception_mode() = odebug;
      if (is_empty())
        throw CImgIOException("CImgList<%s>::load_ffmpeg_external() : Failed to open image sequence '%s'.\n"
                              "Check the filename and if the 'ffmpeg' tool is installed on your system.",
                              pixel_type(),filename);
      return *this;
    }

    static CImgList<T> get_load_ffmpeg_external(const char *const filename) {
      return CImgList<T>().load_ffmpeg_external(filename);
    }

    //! Load a gzipped list, using external tool 'gunzip'.
    CImgList<T>& load_gzip_external(const char *const filename) {
      if (!filename)
        throw CImgIOException("CImg<%s>::load_gzip_external() : Cannot load (null) filename.",
                              pixel_type());
      char command[1024], filetmp[512], body[512];
      const char
        *ext = cimg::split_filename(filename,body),
        *ext2 = cimg::split_filename(body,0);
      cimg_std::FILE *file = 0;
      do {
        if (!cimg::strcasecmp(ext,"gz")) {
          if (*ext2) cimg_std::sprintf(filetmp,"%s%s%s.%s",cimg::temporary_path(),cimg_OS==2?"\\":"/",
                                  cimg::filenamerand(),ext2);
          else cimg_std::sprintf(filetmp,"%s%s%s",cimg::temporary_path(),cimg_OS==2?"\\":"/",
                                  cimg::filenamerand());
        } else {
           if (*ext) cimg_std::sprintf(filetmp,"%s%s%s.%s",cimg::temporary_path(),cimg_OS==2?"\\":"/",
                                  cimg::filenamerand(),ext);
           else cimg_std::sprintf(filetmp,"%s%s%s",cimg::temporary_path(),cimg_OS==2?"\\":"/",
                             cimg::filenamerand());
        }
        if ((file=cimg_std::fopen(filetmp,"rb"))!=0) cimg_std::fclose(file);
      } while (file);
      cimg_std::sprintf(command,"%s -c \"%s\" > %s",cimg::gunzip_path(),filename,filetmp);
      cimg::system(command);
      if (!(file = cimg_std::fopen(filetmp,"rb"))) {
        cimg::fclose(cimg::fopen(filename,"r"));
        throw CImgIOException("CImg<%s>::load_gzip_external() : File '%s' cannot be opened.",
                              pixel_type(),filename);
      } else cimg::fclose(file);
      load(filetmp);
      cimg_std::remove(filetmp);
      return *this;
    }

    static CImgList<T> get_load_gzip_external(const char *const filename) {
      return CImgList<T>().load_gzip_external(filename);
    }

    //! Load a 3D object from a .OFF file.
    template<typename tf, typename tc>
    CImgList<T>& load_off(const char *const filename,
                          CImgList<tf>& primitives, CImgList<tc>& colors,
                          const bool invert_faces=false) {
      return get_load_off(filename,primitives,colors,invert_faces).transfer_to(*this);
    }

    template<typename tf, typename tc>
      static CImgList<T> get_load_off(const char *const filename,
                                      CImgList<tf>& primitives, CImgList<tc>& colors,
                                      const bool invert_faces=false) {
      return CImg<T>().load_off(filename,primitives,colors,invert_faces).get_split('x');
    }

    //! Load a TIFF file.
    CImgList<T>& load_tiff(const char *const filename,
                           const unsigned int first_frame=0, const unsigned int last_frame=~0U,
                           const unsigned int step_frame=1) {
      const unsigned int
        nfirst_frame = first_frame<last_frame?first_frame:last_frame,
        nstep_frame = step_frame?step_frame:1;
      unsigned int nlast_frame = first_frame<last_frame?last_frame:first_frame;
#ifndef cimg_use_tiff
      if (nfirst_frame || nlast_frame!=~0U || nstep_frame!=1)
        throw CImgArgumentException("CImgList<%s>::load_tiff() : File '%s', reading sub-images from a tiff file requires the use of libtiff.\n"
                                    "('cimg_use_tiff' must be defined).",
                                    pixel_type(),filename);
      return assign(CImg<T>::get_load_tiff(filename));
#else
      TIFF *tif = TIFFOpen(filename,"r");
      if (tif) {
        unsigned int nb_images = 0;
        do ++nb_images; while (TIFFReadDirectory(tif));
        if (nfirst_frame>=nb_images || (nlast_frame!=~0U && nlast_frame>=nb_images))
          cimg::warn("CImgList<%s>::load_tiff() : File '%s' contains %u image(s), specified frame range is [%u,%u] (step %u).",
                     pixel_type(),filename,nb_images,nfirst_frame,nlast_frame,nstep_frame);
        if (nfirst_frame>=nb_images) return assign();
        if (nlast_frame>=nb_images) nlast_frame = nb_images-1;
        assign(1+(nlast_frame-nfirst_frame)/nstep_frame);
        TIFFSetDirectory(tif,0);
#if cimg_debug>=3
        TIFFSetWarningHandler(0);
        TIFFSetErrorHandler(0);
#endif
        cimglist_for(*this,l) data[l]._load_tiff(tif,nfirst_frame+l*nstep_frame);
        TIFFClose(tif);
      } else throw CImgException("CImgList<%s>::load_tiff() : File '%s' cannot be opened.",
                                 pixel_type(),filename);
      return *this;
#endif
    }

    static CImgList<T> get_load_tiff(const char *const filename,
                                     const unsigned int first_frame=0, const unsigned int last_frame=~0U,
                                     const unsigned int step_frame=1) {
      return CImgList<T>().load_tiff(filename,first_frame,last_frame,step_frame);
    }

    //! Save an image list into a file.
    /**
       Depending on the extension of the given filename, a file format is chosen for the output file.
    **/
    const CImgList<T>& save(const char *const filename, const int number=-1) const {
      if (is_empty())
        throw CImgInstanceException("CImgList<%s>::save() : File '%s, instance list (%u,%p) is empty.",
                                    pixel_type(),filename?filename:"(null)",size,data);
      if (!filename)
        throw CImgArgumentException("CImg<%s>::save() : Instance list (%u,%p), specified filename is (null).",
                                    pixel_type(),size,data);
      const char *ext = cimg::split_filename(filename);
      char nfilename[1024];
      const char *const fn = (number>=0)?cimg::number_filename(filename,number,6,nfilename):filename;
#ifdef cimglist_save_plugin
      cimglist_save_plugin(fn);
#endif
#ifdef cimglist_save_plugin1
      cimglist_save_plugin1(fn);
#endif
#ifdef cimglist_save_plugin2
      cimglist_save_plugin2(fn);
#endif
#ifdef cimglist_save_plugin3
      cimglist_save_plugin3(fn);
#endif
#ifdef cimglist_save_plugin4
      cimglist_save_plugin4(fn);
#endif
#ifdef cimglist_save_plugin5
      cimglist_save_plugin5(fn);
#endif
#ifdef cimglist_save_plugin6
      cimglist_save_plugin6(fn);
#endif
#ifdef cimglist_save_plugin7
      cimglist_save_plugin7(fn);
#endif
#ifdef cimglist_save_plugin8
      cimglist_save_plugin8(fn);
#endif
#ifdef cimg_use_tiff
      if (!cimg::strcasecmp(ext,"tif") ||
          !cimg::strcasecmp(ext,"tiff")) return save_tiff(fn);
#endif
      if (!cimg::strcasecmp(ext,"cimgz")) return save_cimg(fn,true);
      if (!cimg::strcasecmp(ext,"cimg") || !ext[0]) return save_cimg(fn,false);
      if (!cimg::strcasecmp(ext,"yuv")) return save_yuv(fn,true);
      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,"mkv") ||
          !cimg::strcasecmp(ext,"mpe") ||
          !cimg::strcasecmp(ext,"movie") ||
          !cimg::strcasecmp(ext,"ogm") ||
          !cimg::strcasecmp(ext,"qt") ||
          !cimg::strcasecmp(ext,"rm") ||
          !cimg::strcasecmp(ext,"vob") ||
          !cimg::strcasecmp(ext,"wmv") ||
          !cimg::strcasecmp(ext,"xvid") ||
          !cimg::strcasecmp(ext,"mpeg")) return save_ffmpeg(fn);
      if (!cimg::strcasecmp(ext,"gz")) return save_gzip_external(fn);
      if (size==1) data[0].save(fn,-1); else cimglist_for(*this,l) data[l].save(fn,l);
      return *this;
    }

    //! Save an image sequence, using FFMPEG library.
    // This piece of code has been originally written by David. G. Starkweather.
    const CImgList<T>& save_ffmpeg(const char *const filename, const unsigned int first_frame=0, const unsigned int last_frame=~0U,
                                   const unsigned int fps=25) const {
      if (is_empty())
        throw CImgInstanceException("CImgList<%s>::save_ffmpeg() : File '%s', instance list (%u,%p) is empty.",
                                    pixel_type(),filename?filename:"(null)",size,data);
      if (!filename)
        throw CImgArgumentException("CImgList<%s>::save_ffmpeg() : Instance list (%u,%p), specified filename is (null).",
                                    pixel_type(),size,data);
      if (!fps)
        throw CImgArgumentException("CImgList<%s>::save_ffmpeg() : File '%s', specified framerate is 0.",
                                    pixel_type(),filename);
      const unsigned int nlast_frame = last_frame==~0U?size-1:last_frame;
      if (first_frame>=size || nlast_frame>=size)
        throw CImgArgumentException("CImgList<%s>::save_ffmpeg() : File '%s', specified frames [%u,%u] are out of list range (%u elements).",
                                    pixel_type(),filename,first_frame,last_frame,size);
      for (unsigned int ll = first_frame; ll<=nlast_frame; ++ll) if (!data[ll].is_sameXYZ(data[0]))
        throw CImgInstanceException("CImgList<%s>::save_ffmpeg() : File '%s', images of the sequence have different dimensions.",
                                    pixel_type(),filename);

#ifndef cimg_use_ffmpeg
      return save_ffmpeg_external(filename,first_frame,last_frame);
#else
      avcodec_register_all();
      av_register_all();
      const int
        frame_dimx = data[first_frame].dimx(),
        frame_dimy = data[first_frame].dimy(),
        frame_dimv = data[first_frame].dimv();
      if (frame_dimv!=1 && frame_dimv!=3)
        throw CImgInstanceException("CImgList<%s>::save_ffmpeg() : File '%s', image[0] (%u,%u,%u,%u,%p) has not 1 or 3 channels.",
                                    pixel_type(),filename,data[0].width,data[0].height,data[0].depth,data[0].dim,data);

      PixelFormat dest_pxl_fmt = PIX_FMT_YUV420P;
      PixelFormat src_pxl_fmt  = (frame_dimv == 3)?PIX_FMT_RGB24:PIX_FMT_GRAY8;

      int sws_flags = SWS_FAST_BILINEAR; // Interpolation method (keeping same size images for now).
      AVOutputFormat *fmt = 0;
      fmt = guess_format(0,filename,0);
      if (!fmt) fmt = guess_format("mpeg",0,0); // Default format "mpeg".
      if (!fmt)
        throw CImgArgumentException("CImgList<%s>::save_ffmpeg() : File '%s', could not determine file format from filename.",
                                    pixel_type(),filename);

      AVFormatContext *oc = 0;
      oc = av_alloc_format_context();
      if (!oc) // Failed to allocate format context.
        throw CImgIOException("CImgList<%s>::save_ffmpeg() : File '%s', failed to allocate structure for format context.",
                              pixel_type(),filename);

      AVCodec *codec = 0;
      AVFrame *picture = 0;
      AVFrame *tmp_pict = 0;
      oc->oformat = fmt;
      cimg_std::sprintf(oc->filename,"%s",filename);

      // Add video stream.
      int stream_index = 0;
      AVStream *video_str = 0;
      if (fmt->video_codec!=CODEC_ID_NONE) {
        video_str = av_new_stream(oc,stream_index);
        if (!video_str) { // Failed to allocate stream.
          av_free(oc);
          throw CImgIOException("CImgList<%s>::save_ffmpeg() : File '%s', failed to allocate video stream structure.",
                                pixel_type(),filename);
        }
      } else { // No codec identified.
        av_free(oc);
        throw CImgIOException("CImgList<%s>::save_ffmpeg() : File '%s', no proper codec identified.",
                              pixel_type(),filename);
      }

      AVCodecContext *c = video_str->codec;
      c->codec_id = fmt->video_codec;
      c->codec_type = CODEC_TYPE_VIDEO;
      c->bit_rate = 400000;
      c->width = frame_dimx;
      c->height = frame_dimy;
      c->time_base.num = 1;
      c->time_base.den = fps;
      c->gop_size = 12;
      c->pix_fmt = dest_pxl_fmt;
      if (c->codec_id == CODEC_ID_MPEG2VIDEO) c->max_b_frames = 2;
      if (c->codec_id == CODEC_ID_MPEG1VIDEO) c->mb_decision = 2;

      if (av_set_parameters(oc,0)<0) { // Parameters not properly set.
        av_free(oc);
        throw CImgIOException("CImgList<%s>::save_ffmpeg() : File '%s', parameters for avcodec not properly set.",
                              pixel_type(),filename);
      }

      // Open codecs and alloc buffers.
      codec = avcodec_find_encoder(c->codec_id);
      if (!codec) { // Failed to find codec.
        av_free(oc);
        throw CImgIOException("CImgList<%s>::save_ffmpeg() : File '%s', no codec found.",
                              pixel_type(),filename);
      }
      if (avcodec_open(c,codec)<0) // Failed to open codec.
        throw CImgIOException("CImgList<%s>::save_ffmpeg() : File '%s', failed to open codec.",
                              pixel_type(),filename);
      tmp_pict = avcodec_alloc_frame();
      if (!tmp_pict) { // Failed to allocate memory for tmp_pict frame.
        avcodec_close(video_str->codec);
        av_free(oc);
        throw CImgIOException("CImgList<%s>::save_ffmpeg() : File '%s', failed to allocate memory for data buffer.",
                              pixel_type(),filename);
      }
      tmp_pict->linesize[0] = (src_pxl_fmt==PIX_FMT_RGB24)?3*frame_dimx:frame_dimx;
      tmp_pict->type = FF_BUFFER_TYPE_USER;
      int tmp_size = avpicture_get_size(src_pxl_fmt,frame_dimx,frame_dimy);
      uint8_t *tmp_buffer = (uint8_t*)av_malloc(tmp_size);
      if (!tmp_buffer) { // Failed to allocate memory for tmp buffer.
        av_free(tmp_pict);
        avcodec_close(video_str->codec);
        av_free(oc);
        throw CImgIOException("CImgList<%s>::save_ffmpeg() : File '%s', failed to allocate memory for data buffer.",
                              pixel_type(),filename);
      }

      // Associate buffer with tmp_pict.
      avpicture_fill((AVPicture*)tmp_pict,tmp_buffer,src_pxl_fmt,frame_dimx,frame_dimy);
      picture = avcodec_alloc_frame();
      if (!picture) { // Failed to allocate picture frame.
        av_free(tmp_pict->data[0]);
        av_free(tmp_pict);
        avcodec_close(video_str->codec);
        av_free(oc);
        throw CImgIOException("CImgList<%s>::save_ffmpeg() : File '%s', failed to allocate memory for picture frame.",
                              pixel_type(),filename);
      }

      int size = avpicture_get_size(c->pix_fmt,frame_dimx,frame_dimy);
      uint8_t *buffer = (uint8_t*)av_malloc(size);
      if (!buffer) { // Failed to allocate picture frame buffer.
        av_free(picture);
        av_free(tmp_pict->data[0]);
        av_free(tmp_pict);
        avcodec_close(video_str->codec);
        av_free(oc);
        throw CImgIOException("CImgList<%s>::save_ffmpeg() : File '%s', failed to allocate memory for picture frame buffer.",
                              pixel_type(),filename);
      }

      // Associate the buffer with picture.
      avpicture_fill((AVPicture*)picture,buffer,c->pix_fmt,frame_dimx,frame_dimy);

      // Open file.
      if (!(fmt->flags&AVFMT_NOFILE)) {
        if (url_fopen(&oc->pb,filename,URL_WRONLY)<0)
          throw CImgIOException("CImgList<%s>::save_ffmpeg() : File '%s' cannot be opened.",
                                pixel_type(),filename);
      }

      if (av_write_header(oc)<0)
        throw CImgIOException("CImgList<%s>::save_ffmpeg() : File '%s', could not write header.",
                              pixel_type(),filename);
      double video_pts;
      SwsContext *img_convert_context = 0;
      img_convert_context = sws_getContext(frame_dimx,frame_dimy,src_pxl_fmt,
                                           c->width,c->height,c->pix_fmt,sws_flags,0,0,0);
      if (!img_convert_context) { // Failed to get swscale context.
        // if (!(fmt->flags & AVFMT_NOFILE)) url_fclose(&oc->pb);
        av_free(picture->data);
        av_free(picture);
        av_free(tmp_pict->data[0]);
        av_free(tmp_pict);
        avcodec_close(video_str->codec);
        av_free(oc);
        throw CImgIOException("CImgList<%s>::save_ffmpeg() : File '%', failed to get conversion context.",
                              pixel_type(),filename);
      }
      int ret = 0, out_size;
      uint8_t *video_outbuf = 0;
      int video_outbuf_size = 1000000;
      video_outbuf = (uint8_t*)av_malloc(video_outbuf_size);
      if (!video_outbuf) {
        // if (!(fmt->flags & AVFMT_NOFILE)) url_fclose(&oc->pb);
        av_free(picture->data);
        av_free(picture);
        av_free(tmp_pict->data[0]);
        av_free(tmp_pict);
        avcodec_close(video_str->codec);
        av_free(oc);
        throw CImgIOException("CImgList<%s>::save_ffmpeg() : File '%s', memory allocation error.",
                              pixel_type(),filename);
      }

      // Loop through each desired image in list.
      for (unsigned int i = first_frame; i<=nlast_frame; ++i) {
        CImg<uint8_t> currentIm = data[i], red, green, blue, gray;
        if (src_pxl_fmt == PIX_FMT_RGB24) {
          red = currentIm.get_shared_channel(0);
          green = currentIm.get_shared_channel(1);
          blue = currentIm.get_shared_channel(2);
          cimg_forXY(currentIm,X,Y) { // Assign pizel values to data buffer in interlaced RGBRGB ... format.
            tmp_pict->data[0][Y*tmp_pict->linesize[0] + 3*X]     = red(X,Y);
            tmp_pict->data[0][Y*tmp_pict->linesize[0] + 3*X + 1] = green(X,Y);
            tmp_pict->data[0][Y*tmp_pict->linesize[0] + 3*X + 2] = blue(X,Y);
          }
        } else {
          gray = currentIm.get_shared_channel(0);
          cimg_forXY(currentIm,X,Y) tmp_pict->data[0][Y*tmp_pict->linesize[0] + X] = gray(X,Y);
        }

        if (video_str) video_pts = (video_str->pts.val * video_str->time_base.num)/(video_str->time_base.den);
        else video_pts = 0.0;
        if (!video_str) break;
        if (sws_scale(img_convert_context,tmp_pict->data,tmp_pict->linesize,0,c->height,picture->data,picture->linesize)<0) break;
        out_size = avcodec_encode_video(c,video_outbuf,video_outbuf_size,picture);
        if (out_size>0) {
          AVPacket pkt;
          av_init_packet(&pkt);
          pkt.pts = av_rescale_q(c->coded_frame->pts,c->time_base,video_str->time_base);
          if (c->coded_frame->key_frame) pkt.flags|=PKT_FLAG_KEY;
          pkt.stream_index = video_str->index;
          pkt.data = video_outbuf;
          pkt.size = out_size;
          ret = av_write_frame(oc,&pkt);
        } else if (out_size<0) break;
        if (ret) break; // Error occured in writing frame.
      }

      // Close codec.
      if (video_str) {
        avcodec_close(video_str->codec);
        av_free(picture->data[0]);
        av_free(picture);
        av_free(tmp_pict->data[0]);
        av_free(tmp_pict);
      }
      if (av_write_trailer(oc)<0)
        throw CImgIOException("CImgList<%s>::save_ffmpeg() : File '%s', failed to write trailer.",
                              pixel_type(),filename);
      av_freep(&oc->streams[stream_index]->codec);
      av_freep(&oc->streams[stream_index]);
      if (!(fmt->flags&AVFMT_NOFILE)) {
        /*if (url_fclose(oc->pb)<0)
          throw CImgIOException("CImgList<%s>::save_ffmpeg() : File '%s', failed to close file.",
          pixel_type(),filename);
        */
      }
      av_free(oc);
      av_free(video_outbuf);
#endif
      return *this;
    }

    // Save an image sequence into a YUV file (internal).
    const CImgList<T>& _save_yuv(cimg_std::FILE *const file, const char *const filename, const bool rgb2yuv) const {
      if (is_empty())
        throw CImgInstanceException("CImgList<%s>::save_yuv() : File '%s', instance list (%u,%p) is empty.",
                                    pixel_type(),filename?filename:"(FILE*)",size,data);
      if (!file && !filename)
        throw CImgArgumentException("CImg<%s>::save_yuv() : Instance list (%u,%p), specified file is (null).",
                                    pixel_type(),size,data);
      if ((*this)[0].dimx()%2 || (*this)[0].dimy()%2)
        throw CImgInstanceException("CImgList<%s>::save_yuv() : File '%s', image dimensions must be even numbers (current are %ux%u).",
                                    pixel_type(),filename?filename:"(FILE*)",(*this)[0].dimx(),(*this)[0].dimy());

      cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"wb");
      cimglist_for(*this,l) {
        CImg<ucharT> YCbCr((*this)[l]);
        if (rgb2yuv) YCbCr.RGBtoYCbCr();
        cimg::fwrite(YCbCr.data,YCbCr.width*YCbCr.height,nfile);
        cimg::fwrite(YCbCr.get_resize(YCbCr.width/2, YCbCr.height/2,1,3,3).ptr(0,0,0,1),
                     YCbCr.width*YCbCr.height/2,nfile);
      }
      if (!file) cimg::fclose(nfile);
      return *this;
    }

    //! Save an image sequence into a YUV file.
    const CImgList<T>& save_yuv(const char *const filename=0, const bool rgb2yuv=true) const {
      return _save_yuv(0,filename,rgb2yuv);
    }

    //! Save an image sequence into a YUV file.
    const CImgList<T>& save_yuv(cimg_std::FILE *const file, const bool rgb2yuv=true) const {
      return _save_yuv(file,0,rgb2yuv);
    }

    //! Save an image list into a .cimg file.
    /**
       A CImg RAW file is a simple uncompressed binary file that may be used to save list of CImg<T> images.
       \param filename : name of the output file.
       \return A reference to the current CImgList instance is returned.
    **/
    const CImgList<T>& _save_cimg(cimg_std::FILE *const file, const char *const filename, const bool compression) const {
      if (is_empty())
        throw CImgInstanceException("CImgList<%s>::save_cimg() : File '%s', instance list (%u,%p) is empty.",
                                    pixel_type(),filename?filename:"(FILE*)",size,data);
      if (!file && !filename)
        throw CImgArgumentException("CImg<%s>::save_cimg() : Instance list (%u,%p), specified file is (null).",
                                    pixel_type(),size,data);
      cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"wb");
      const char *const ptype = pixel_type(), *const etype = cimg::endianness()?"big":"little";
      if (cimg_std::strstr(ptype,"unsigned")==ptype) cimg_std::fprintf(nfile,"%u unsigned_%s %s_endian\n",size,ptype+9,etype);
      else cimg_std::fprintf(nfile,"%u %s %s_endian\n",size,ptype,etype);
      cimglist_for(*this,l) {
        const CImg<T>& img = data[l];
        cimg_std::fprintf(nfile,"%u %u %u %u",img.width,img.height,img.depth,img.dim);
        if (img.data) {
          CImg<T> tmp;
          if (cimg::endianness()) { tmp = img; cimg::invert_endianness(tmp.data,tmp.size()); }
          const CImg<T>& ref = cimg::endianness()?tmp:img;
          bool compressed = false;
          if (compression) {
#ifdef cimg_use_zlib
            const unsigned long siz = sizeof(T)*ref.size();
            unsigned long csiz = siz + siz/10 + 16;
            Bytef *const cbuf = new Bytef[csiz];
            if (compress(cbuf,&csiz,(Bytef*)ref.data,siz)) {
              cimg::warn("CImgList<%s>::save_cimg() : File '%s', failed to save compressed data.\n Data will be saved uncompressed.",
                       pixel_type(),filename?filename:"(FILE*)");
              compressed = false;
            } else {
              cimg_std::fprintf(nfile," #%lu\n",csiz);
              cimg::fwrite(cbuf,csiz,nfile);
              delete[] cbuf;
              compressed = true;
            }
#else
            cimg::warn("CImgList<%s>::save_cimg() : File '%s', cannot save compressed data unless zlib is used "
                       "('cimg_use_zlib' must be defined).\n Data will be saved uncompressed.",
                       pixel_type(),filename?filename:"(FILE*)");
            compressed = false;
#endif
          }
          if (!compressed) {
            cimg_std::fputc('\n',nfile);
            cimg::fwrite(ref.data,ref.size(),nfile);
          }
        } else cimg_std::fputc('\n',nfile);
      }
      if (!file) cimg::fclose(nfile);
      return *this;
    }

    //! Save an image list into a CImg file (RAW binary file + simple header)
    const CImgList<T>& save_cimg(cimg_std::FILE *file, const bool compress=false) const {
      return _save_cimg(file,0,compress);
    }

    //! Save an image list into a CImg file (RAW binary file + simple header)
    const CImgList<T>& save_cimg(const char *const filename, const bool compress=false) const {
      return _save_cimg(0,filename,compress);
    }

    // Insert the instance image into an existing .cimg file, at specified coordinates.
    const CImgList<T>& _save_cimg(cimg_std::FILE *const file, const char *const filename,
                                 const unsigned int n0,
                                 const unsigned int x0, const unsigned int y0,
                                 const unsigned int z0, const unsigned int v0) const {
#define _cimg_save_cimg_case(Ts,Tss) \
      if (!saved && !cimg::strcasecmp(Ts,str_pixeltype)) { \
        for (unsigned int l=0; l<lmax; ++l) { \
          j = 0; while((i=cimg_std::fgetc(nfile))!='\n') tmp[j++]=(char)i; tmp[j]='\0'; \
          W = H = D = V = 0; \
          if (cimg_std::sscanf(tmp,"%u %u %u %u",&W,&H,&D,&V)!=4) \
            throw CImgIOException("CImgList<%s>::save_cimg() : File '%s', Image %u has an invalid size (%u,%u,%u,%u)\n", \
                                  pixel_type(), filename?filename:("(FILE*)"), W, H, D, V); \
          if (W*H*D*V>0) { \
            if (l<n0 || x0>=W || y0>=H || z0>=D || v0>=D) cimg_std::fseek(nfile,W*H*D*V*sizeof(Tss),SEEK_CUR); \
            else { \
              const CImg<T>& img = (*this)[l-n0]; \
              const T *ptrs = img.data; \
              const unsigned int \
                x1 = x0 + img.width - 1, \
                y1 = y0 + img.height - 1, \
                z1 = z0 + img.depth - 1, \
                v1 = v0 + img.dim - 1, \
                nx1 = x1>=W?W-1:x1, \
                ny1 = y1>=H?H-1:y1, \
                nz1 = z1>=D?D-1:z1, \
                nv1 = v1>=V?V-1:v1; \
              CImg<Tss> raw(1+nx1-x0); \
              const unsigned int skipvb = v0*W*H*D*sizeof(Tss); \
              if (skipvb) cimg_std::fseek(nfile,skipvb,SEEK_CUR); \
              for (unsigned int v=1+nv1-v0; v; --v) { \
                const unsigned int skipzb = z0*W*H*sizeof(Tss); \
                if (skipzb) cimg_std::fseek(nfile,skipzb,SEEK_CUR); \
                for (unsigned int z=1+nz1-z0; z; --z) { \
                  const unsigned int skipyb = y0*W*sizeof(Tss); \
                  if (skipyb) cimg_std::fseek(nfile,skipyb,SEEK_CUR); \
                  for (unsigned int y=1+ny1-y0; y; --y) { \
                    const unsigned int skipxb = x0*sizeof(Tss); \
                    if (skipxb) cimg_std::fseek(nfile,skipxb,SEEK_CUR); \
                    raw.assign(ptrs, raw.width); \
                    ptrs+=img.width; \
                    if (endian) cimg::invert_endianness(raw.data,raw.width); \
                    cimg::fwrite(raw.data,raw.width,nfile); \
                    const unsigned int skipxe = (W-1-nx1)*sizeof(Tss); \
                    if (skipxe) cimg_std::fseek(nfile,skipxe,SEEK_CUR); \
                  } \
                  const unsigned int skipye = (H-1-ny1)*W*sizeof(Tss); \
                  if (skipye) cimg_std::fseek(nfile,skipye,SEEK_CUR); \
                } \
                const unsigned int skipze = (D-1-nz1)*W*H*sizeof(Tss); \
                if (skipze) cimg_std::fseek(nfile,skipze,SEEK_CUR); \
              } \
              const unsigned int skipve = (V-1-nv1)*W*H*D*sizeof(Tss); \
              if (skipve) cimg_std::fseek(nfile,skipve,SEEK_CUR); \
            } \
          } \
        } \
        saved = true; \
      }
      if (is_empty())
        throw CImgInstanceException("CImgList<%s>::save_cimg() : File '%s', instance list (%u,%p) is empty.",
                                    pixel_type(),filename?filename:"(FILE*)",size,data);
      if (!file && !filename)
        throw CImgArgumentException("CImg<%s>::save_cimg() : Instance list (%u,%p), specified file is (null).",
                                    pixel_type(),size,data);
      typedef unsigned char uchar;
      typedef unsigned short ushort;
      typedef unsigned int uint;
      typedef unsigned long ulong;
      cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"rb+");
      bool saved = false, endian = cimg::endianness();
      char tmp[256], str_pixeltype[256], str_endian[256];
      unsigned int j, err, N, W, H, D, V;
      int i;
      j = 0; while((i=cimg_std::fgetc(nfile))!='\n' && i!=EOF && j<256) tmp[j++] = (char)i; tmp[j] = '\0';
      err = cimg_std::sscanf(tmp,"%u%*c%255[A-Za-z_]%*c%255[sA-Za-z_ ]",&N,str_pixeltype,str_endian);
      if (err<2) {
        if (!file) cimg::fclose(nfile);
        throw CImgIOException("CImgList<%s>::save_cimg() : File '%s', Unknow CImg RAW header.",
                              pixel_type(),filename?filename:"(FILE*)");
      }
      if (!cimg::strncasecmp("little",str_endian,6)) endian = false;
      else if (!cimg::strncasecmp("big",str_endian,3)) endian = true;
      const unsigned int lmax = cimg::min(N,n0+size);
      _cimg_save_cimg_case("bool",bool);
      _cimg_save_cimg_case("unsigned_char",uchar);
      _cimg_save_cimg_case("uchar",uchar);
      _cimg_save_cimg_case("char",char);
      _cimg_save_cimg_case("unsigned_short",ushort);
      _cimg_save_cimg_case("ushort",ushort);
      _cimg_save_cimg_case("short",short);
      _cimg_save_cimg_case("unsigned_int",uint);
      _cimg_save_cimg_case("uint",uint);
      _cimg_save_cimg_case("int",int);
      _cimg_save_cimg_case("unsigned_long",ulong);
      _cimg_save_cimg_case("ulong",ulong);
      _cimg_save_cimg_case("long",long);
      _cimg_save_cimg_case("float",float);
      _cimg_save_cimg_case("double",double);
      if (!saved) {
        if (!file) cimg::fclose(nfile);
        throw CImgIOException("CImgList<%s>::save_cimg() : File '%s', cannot save images of pixels coded as '%s'.",
                              pixel_type(),filename?filename:"(FILE*)",str_pixeltype);
      }
      if (!file) cimg::fclose(nfile);
      return *this;
    }

    //! Insert the instance image into an existing .cimg file, at specified coordinates.
    const CImgList<T>& save_cimg(const char *const filename,
                                 const unsigned int n0,
                                 const unsigned int x0, const unsigned int y0,
                                 const unsigned int z0, const unsigned int v0) const {
      return _save_cimg(0,filename,n0,x0,y0,z0,v0);
    }

    //! Insert the instance image into an existing .cimg file, at specified coordinates.
    const CImgList<T>& save_cimg(cimg_std::FILE *const file,
                                 const unsigned int n0,
                                 const unsigned int x0, const unsigned int y0,
                                 const unsigned int z0, const unsigned int v0) const {
      return _save_cimg(file,0,n0,x0,y0,z0,v0);
    }

    // Create an empty .cimg file with specified dimensions (internal)
    static void _save_empty_cimg(cimg_std::FILE *const file, const char *const filename,
                                const unsigned int nb,
                                const unsigned int dx, const unsigned int dy,
                                const unsigned int dz, const unsigned int dv) {
      cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"wb");
      const unsigned int siz = dx*dy*dz*dv*sizeof(T);
      cimg_std::fprintf(nfile,"%u %s\n",nb,pixel_type());
      for (unsigned int i=nb; i; --i) {
        cimg_std::fprintf(nfile,"%u %u %u %u\n",dx,dy,dz,dv);
        for (unsigned int off=siz; off; --off) cimg_std::fputc(0,nfile);
      }
      if (!file) cimg::fclose(nfile);
    }

    //! Create an empty .cimg file with specified dimensions.
    static void save_empty_cimg(const char *const filename,
                                const unsigned int nb,
                                const unsigned int dx, const unsigned int dy=1,
                                const unsigned int dz=1, const unsigned int dv=1) {
      return _save_empty_cimg(0,filename,nb,dx,dy,dz,dv);
    }

    //! Create an empty .cimg file with specified dimensions.
    static void save_empty_cimg(cimg_std::FILE *const file,
                                const unsigned int nb,
                                const unsigned int dx, const unsigned int dy=1,
                                const unsigned int dz=1, const unsigned int dv=1) {
      return _save_empty_cimg(file,0,nb,dx,dy,dz,dv);
    }

    //! Save a file in TIFF format.
#ifdef cimg_use_tiff
    const CImgList<T>& save_tiff(const char *const filename) const {
      if (is_empty())
        throw CImgInstanceException("CImgList<%s>::save_tiff() : File '%s', instance list (%u,%p) is empty.",
                                    pixel_type(),filename?filename:"(null)",size,data);
      if (!filename)
        throw CImgArgumentException("CImgList<%s>::save_tiff() : Specified filename is (null) for instance list (%u,%p).",
                                    pixel_type(),size,data);
      TIFF *tif = TIFFOpen(filename,"w");
      if (tif) {
        for (unsigned int dir=0, l=0; l<size; ++l) {
          const CImg<T>& img = (*this)[l];
          if (img) {
            if (img.depth==1) img._save_tiff(tif,dir++);
            else cimg_forZ(img,z) img.get_slice(z)._save_tiff(tif,dir++);
          }
        }
        TIFFClose(tif);
      } else
        throw CImgException("CImgList<%s>::save_tiff() : File '%s', error while opening stream for tiff file.",
                            pixel_type(),filename);
      return *this;
    }
#endif

    //! Save an image list as a gzipped file, using external tool 'gzip'.
    const CImgList<T>& save_gzip_external(const char *const filename) const {
      if (!filename)
        throw CImgIOException("CImg<%s>::save_gzip_external() : Cannot save (null) filename.",
                              pixel_type());
      char command[1024], filetmp[512], body[512];
      const char
        *ext = cimg::split_filename(filename,body),
        *ext2 = cimg::split_filename(body,0);
      cimg_std::FILE *file;
      do {
        if (!cimg::strcasecmp(ext,"gz")) {
          if (*ext2) cimg_std::sprintf(filetmp,"%s%s%s.%s",cimg::temporary_path(),cimg_OS==2?"\\":"/",
                                  cimg::filenamerand(),ext2);
          else cimg_std::sprintf(filetmp,"%s%s%s.cimg",cimg::temporary_path(),cimg_OS==2?"\\":"/",
                            cimg::filenamerand());
        } else {
          if (*ext) cimg_std::sprintf(filetmp,"%s%s%s.%s",cimg::temporary_path(),cimg_OS==2?"\\":"/",
                                 cimg::filenamerand(),ext);
          else cimg_std::sprintf(filetmp,"%s%s%s.cimg",cimg::temporary_path(),cimg_OS==2?"\\":"/",
                                 cimg::filenamerand());
        }
        if ((file=cimg_std::fopen(filetmp,"rb"))!=0) cimg_std::fclose(file);
      } while (file);
      save(filetmp);
      cimg_std::sprintf(command,"%s -c %s > \"%s\"",cimg::gzip_path(),filetmp,filename);
      cimg::system(command);
      file = cimg_std::fopen(filename,"rb");
      if (!file)
        throw CImgIOException("CImgList<%s>::save_gzip_external() : File '%s' cannot be saved.",
                              pixel_type(),filename);
      else cimg::fclose(file);
      cimg_std::remove(filetmp);
      return *this;
    }

    //! Save an image list into a OFF file.
    template<typename tf, typename tc>
    const CImgList<T>& save_off(const char *const filename,
                                const CImgList<tf>& primitives, const CImgList<tc>& colors, const bool invert_faces=false) const {
      get_append('x','y').save_off(filename,primitives,colors,invert_faces);
      return *this;
    }

    //! Save an image list into a OFF file.
    template<typename tf, typename tc>
    const CImgList<T>& save_off(cimg_std::FILE *const file,
                                const CImgList<tf>& primitives, const CImgList<tc>& colors, const bool invert_faces=false) const {
      get_append('x','y').save_off(file,primitives,colors,invert_faces);
      return *this;
    }

    //! Save an image sequence using the external tool 'ffmpeg'.
    const CImgList<T>& save_ffmpeg_external(const char *const filename, const unsigned int first_frame=0, const unsigned int last_frame=~0U,
                                            const char *const codec="mpeg2video") const {
      if (is_empty())
        throw CImgInstanceException("CImgList<%s>::save_ffmpeg_external() : File '%s', instance list (%u,%p) is empty.",
                                    pixel_type(),filename?filename:"(null)",size,data);
      if (!filename)
        throw CImgArgumentException("CImgList<%s>::save_ffmpeg_external() : Instance list (%u,%p), specified filename is (null).",
                                    pixel_type(),size,data);
      char command[1024], filetmp[512], filetmp2[512];
      cimg_std::FILE *file = 0;
      const unsigned int nlast_frame = last_frame==~0U?size-1:last_frame;
      if (first_frame>=size || nlast_frame>=size)
        throw CImgArgumentException("CImgList<%s>::save_ffmpeg_external() : File '%s', specified frames [%u,%u] are out of list range (%u elements).",
                                    pixel_type(),filename,first_frame,last_frame,size);
      for (unsigned int ll = first_frame; ll<=nlast_frame; ++ll) if (!data[ll].is_sameXYZ(data[0]))
        throw CImgInstanceException("CImgList<%s>::save_ffmpeg_external() : File '%s', all images of the sequence must be of the same dimension.",
                                    pixel_type(),filename);
      do {
        cimg_std::sprintf(filetmp,"%s%s%s",cimg::temporary_path(),cimg_OS==2?"\\":"/",cimg::filenamerand());
        cimg_std::sprintf(filetmp2,"%s_000001.ppm",filetmp);
        if ((file=cimg_std::fopen(filetmp2,"rb"))!=0) cimg_std::fclose(file);
      } while (file);
      for (unsigned int l = first_frame; l<=nlast_frame; ++l) {
        cimg_std::sprintf(filetmp2,"%s_%.6u.ppm",filetmp,l+1);
        if (data[l].depth>1 || data[l].dim!=3) data[l].get_resize(-100,-100,1,3).save_pnm(filetmp2);
        else data[l].save_pnm(filetmp2);
      }
#if cimg_OS!=2
      cimg_std::sprintf(command,"ffmpeg -i %s_%%6d.ppm -vcodec %s -sameq -y \"%s\" >/dev/null 2>&1",filetmp,codec,filename);
#else
      cimg_std::sprintf(command,"\"ffmpeg -i %s_%%6d.ppm -vcodec %s -sameq -y \"%s\"\" >NUL 2>&1",filetmp,codec,filename);
#endif
      cimg::system(command);
      file = cimg_std::fopen(filename,"rb");
      if (!file)
        throw CImgIOException("CImg<%s>::save_ffmpeg_external() : Failed to save image sequence '%s'.\n\n",
                              pixel_type(),filename);
      else cimg::fclose(file);
      cimglist_for(*this,lll) { cimg_std::sprintf(filetmp2,"%s_%.6u.ppm",filetmp,lll+1); cimg_std::remove(filetmp2); }
      return *this;
    }

   };

  /*
   #---------------------------------------------
   #
   # Completion of previously declared functions
   #
   #----------------------------------------------
  */

namespace cimg {

  //! Display a dialog box, where a user can click standard buttons.
  /**
     Up to 6 buttons can be defined in the dialog window.
     This function returns when a user clicked one of the button or closed the dialog window.
     \param title = Title of the dialog window.
     \param msg = Main message displayed inside the dialog window.
     \param button1_txt = Label of the 1st button.
     \param button2_txt = Label of the 2nd button.
     \param button3_txt = Label of the 3rd button.
     \param button4_txt = Label of the 4th button.
     \param button5_txt = Label of the 5th button.
     \param button6_txt = Label of the 6th button.
     \param logo = Logo image displayed at the left of the main message. This parameter is optional.
     \param centering = Tell to center the dialog window on the screen.
     \return The button number (from 0 to 5), or -1 if the dialog window has been closed by the user.
     \note If a button text is set to 0, then the corresponding button (and the followings) won't appear in
     the dialog box. At least one button is necessary.
  **/

  template<typename t>
  inline int dialog(const char *title, const char *msg,
                    const char *button1_txt, const char *button2_txt,
                    const char *button3_txt, const char *button4_txt,
                    const char *button5_txt, const char *button6_txt,
                    const CImg<t>& logo, const bool centering = false) {
#if cimg_display!=0
    const unsigned char
      black[] = { 0,0,0 }, white[] = { 255,255,255 }, gray[] = { 200,200,200 }, gray2[] = { 150,150,150 };

      // Create buttons and canvas graphics
      CImgList<unsigned char> buttons, cbuttons, sbuttons;
      if (button1_txt) { buttons.insert(CImg<unsigned char>().draw_text(0,0,button1_txt,black,gray,1,13));
      if (button2_txt) { buttons.insert(CImg<unsigned char>().draw_text(0,0,button2_txt,black,gray,1,13));
      if (button3_txt) { buttons.insert(CImg<unsigned char>().draw_text(0,0,button3_txt,black,gray,1,13));
      if (button4_txt) { buttons.insert(CImg<unsigned char>().draw_text(0,0,button4_txt,black,gray,1,13));
      if (button5_txt) { buttons.insert(CImg<unsigned char>().draw_text(0,0,button5_txt,black,gray,1,13));
      if (button6_txt) { buttons.insert(CImg<unsigned char>().draw_text(0,0,button6_txt,black,gray,1,13));
      }}}}}}
      if (!buttons.size)
        throw CImgArgumentException("cimg::dialog() : No buttons have been defined. At least one is necessary");

      unsigned int bw = 0, bh = 0;
      cimglist_for(buttons,l) { bw = cimg::max(bw,buttons[l].width); bh = cimg::max(bh,buttons[l].height); }
      bw+=8; bh+=8;
      if (bw<64) bw=64;
      if (bw>128) bw=128;
      if (bh<24) bh=24;
      if (bh>48) bh=48;

      CImg<unsigned char> button(bw,bh,1,3);
      button.draw_rectangle(0,0,bw-1,bh-1,gray);
      button.draw_line(0,0,bw-1,0,white).draw_line(0,bh-1,0,0,white);
      button.draw_line(bw-1,0,bw-1,bh-1,black).draw_line(bw-1,bh-1,0,bh-1,black);
      button.draw_line(1,bh-2,bw-2,bh-2,gray2).draw_line(bw-2,bh-2,bw-2,1,gray2);
      CImg<unsigned char> sbutton(bw,bh,1,3);
      sbutton.draw_rectangle(0,0,bw-1,bh-1,gray);
      sbutton.draw_line(0,0,bw-1,0,black).draw_line(bw-1,0,bw-1,bh-1,black);
      sbutton.draw_line(bw-1,bh-1,0,bh-1,black).draw_line(0,bh-1,0,0,black);
      sbutton.draw_line(1,1,bw-2,1,white).draw_line(1,bh-2,1,1,white);
      sbutton.draw_line(bw-2,1,bw-2,bh-2,black).draw_line(bw-2,bh-2,1,bh-2,black);
      sbutton.draw_line(2,bh-3,bw-3,bh-3,gray2).draw_line(bw-3,bh-3,bw-3,2,gray2);
      sbutton.draw_line(4,4,bw-5,4,black,1,0xAAAAAAAA,true).draw_line(bw-5,4,bw-5,bh-5,black,1,0xAAAAAAAA,false);
      sbutton.draw_line(bw-5,bh-5,4,bh-5,black,1,0xAAAAAAAA,false).draw_line(4,bh-5,4,4,black,1,0xAAAAAAAA,false);
      CImg<unsigned char> cbutton(bw,bh,1,3);
      cbutton.draw_rectangle(0,0,bw-1,bh-1,black).draw_rectangle(1,1,bw-2,bh-2,gray2).draw_rectangle(2,2,bw-3,bh-3,gray);
      cbutton.draw_line(4,4,bw-5,4,black,1,0xAAAAAAAA,true).draw_line(bw-5,4,bw-5,bh-5,black,1,0xAAAAAAAA,false);
      cbutton.draw_line(bw-5,bh-5,4,bh-5,black,1,0xAAAAAAAA,false).draw_line(4,bh-5,4,4,black,1,0xAAAAAAAA,false);

      cimglist_for(buttons,ll) {
        cbuttons.insert(CImg<unsigned char>(cbutton).draw_image(1+(bw-buttons[ll].dimx())/2,1+(bh-buttons[ll].dimy())/2,buttons[ll]));
        sbuttons.insert(CImg<unsigned char>(sbutton).draw_image((bw-buttons[ll].dimx())/2,(bh-buttons[ll].dimy())/2,buttons[ll]));
        buttons[ll] = CImg<unsigned char>(button).draw_image((bw-buttons[ll].dimx())/2,(bh-buttons[ll].dimy())/2,buttons[ll]);
      }

      CImg<unsigned char> canvas;
      if (msg) canvas = CImg<unsigned char>().draw_text(0,0,msg,black,gray,1,13);
      const unsigned int
        bwall = (buttons.size-1)*(12+bw) + bw,
        w = cimg::max(196U,36+logo.width+canvas.width, 24+bwall),
        h = cimg::max(96U,36+canvas.height+bh,36+logo.height+bh),
        lx = 12 + (canvas.data?0:((w-24-logo.width)/2)),
        ly = (h-12-bh-logo.height)/2,
        tx = lx+logo.width+12,
        ty = (h-12-bh-canvas.height)/2,
        bx = (w-bwall)/2,
        by = h-12-bh;

      if (canvas.data)
        canvas = CImg<unsigned char>(w,h,1,3).
          draw_rectangle(0,0,w-1,h-1,gray).
          draw_line(0,0,w-1,0,white).draw_line(0,h-1,0,0,white).
          draw_line(w-1,0,w-1,h-1,black).draw_line(w-1,h-1,0,h-1,black).
          draw_image(tx,ty,canvas);
      else
        canvas = CImg<unsigned char>(w,h,1,3).
          draw_rectangle(0,0,w-1,h-1,gray).
          draw_line(0,0,w-1,0,white).draw_line(0,h-1,0,0,white).
          draw_line(w-1,0,w-1,h-1,black).draw_line(w-1,h-1,0,h-1,black);
      if (logo.data) canvas.draw_image(lx,ly,logo);

      unsigned int xbuttons[6];
      cimglist_for(buttons,lll) { xbuttons[lll] = bx+(bw+12)*lll; canvas.draw_image(xbuttons[lll],by,buttons[lll]); }

      // Open window and enter events loop
      CImgDisplay disp(canvas,title?title:" ",0,false,centering?true:false);
      if (centering) disp.move((CImgDisplay::screen_dimx()-disp.dimx())/2,
                               (CImgDisplay::screen_dimy()-disp.dimy())/2);
      bool stopflag = false, refresh = false;
      int oselected = -1, oclicked = -1, selected = -1, clicked = -1;
      while (!disp.is_closed && !stopflag) {
        if (refresh) {
          if (clicked>=0) CImg<unsigned char>(canvas).draw_image(xbuttons[clicked],by,cbuttons[clicked]).display(disp);
          else {
            if (selected>=0) CImg<unsigned char>(canvas).draw_image(xbuttons[selected],by,sbuttons[selected]).display(disp);
            else canvas.display(disp);
          }
          refresh = false;
        }
        disp.wait(15);
        if (disp.is_resized) disp.resize(disp);

        if (disp.button&1)  {
          oclicked = clicked;
          clicked = -1;
          cimglist_for(buttons,l)
            if (disp.mouse_y>=(int)by && disp.mouse_y<(int)(by+bh) &&
                disp.mouse_x>=(int)xbuttons[l] && disp.mouse_x<(int)(xbuttons[l]+bw)) {
              clicked = selected = l;
              refresh = true;
            }
          if (clicked!=oclicked) refresh = true;
        } else if (clicked>=0) stopflag = true;

        if (disp.key) {
          oselected = selected;
          switch (disp.key) {
          case cimg::keyESC : selected=-1; stopflag=true; break;
          case cimg::keyENTER : if (selected<0) selected = 0; stopflag = true; break;
          case cimg::keyTAB :
          case cimg::keyARROWRIGHT :
          case cimg::keyARROWDOWN : selected = (selected+1)%buttons.size; break;
          case cimg::keyARROWLEFT :
          case cimg::keyARROWUP : selected = (selected+buttons.size-1)%buttons.size; break;
          }
          disp.key = 0;
          if (selected!=oselected) refresh = true;
        }
      }
      if (!disp) selected = -1;
      return selected;
#else
      cimg_std::fprintf(cimg_stdout,"<%s>\n\n%s\n\n",title,msg);
      return -1+0*(int)(button1_txt-button2_txt+button3_txt-button4_txt+button5_txt-button6_txt+logo.width+(int)centering);
#endif
  }

  inline int dialog(const char *title, const char *msg,
                    const char *button1_txt, const char *button2_txt, const char *button3_txt,
                    const char *button4_txt, const char *button5_txt, const char *button6_txt,
                    const bool centering) {
    return dialog(title,msg,button1_txt,button2_txt,button3_txt,button4_txt,button5_txt,button6_txt,
                  CImg<unsigned char>::logo40x38(),centering);
  }

  // End of cimg:: namespace
}

  // End of cimg_library:: namespace
}

#ifdef _cimg_redefine_min
#define min(a,b) (((a)<(b))?(a):(b))
#endif
#ifdef _cimg_redefine_max
#define max(a,b) (((a)>(b))?(a):(b))
#endif

#endif
// Local Variables:
// mode: c++
// End:
