changeset 0:99141f27dfc3

Initial commit.
author benjf5 <benjf5@gmail.com>
date Thu, 12 Apr 2012 09:23:16 -0400
parents
children b3fb5e70868a
files CImg.h Complex_Polynomial.h Newton Fractal.cpp
diffstat 3 files changed, 44957 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
new file mode 100644
--- /dev/null
+++ b/CImg.h
@@ -0,0 +1,44792 @@
+/*
+ #
+ #  File            : CImg.h
+ #                    ( C++ header file )
+ #
+ #  Description     : The C++ Template Image Processing Toolkit.
+ #                    This file is the main component of the CImg Library project.
+ #                    ( http://cimg.sourceforge.net )
+ #
+ #  Project manager : David Tschumperle.
+ #                    ( http://www.greyc.ensicaen.fr/~dtschump/ )
+ #
+ #                    The complete list of contributors is available in file 'README.txt'
+ #                    distributed within the CImg package.
+ #
+ #  Licenses        : This file is 'dual-licensed', you have to choose one
+ #                    of the two licenses below to apply.
+ #
+ #                    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.
+ #
+*/
+
+// Set version number of the library.
+#ifndef cimg_version
+#define cimg_version 150
+
+/*-----------------------------------------------------------
+ #
+ # Test and possibly auto-set CImg configuration variables
+ # and include required headers.
+ #
+ # If you find that default configuration variables are
+ # not adapted to your system, you can override their values
+ # before including the header file "CImg.h"
+ # (use the #define directive).
+ #
+ ------------------------------------------------------------*/
+
+// Include required standard C++ headers.
+// This is the minimal set of required headers to make CImg-based codes compile.
+#include <cstdio>
+#include <cstdlib>
+#include <cstdarg>
+#include <cstring>
+#include <cmath>
+#include <ctime>
+#include <exception>
+
+// Detect/configure operating system variables.
+//
+// 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.
+//                       (auto-detection is done by default).
+#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 : Invalid configuration variable 'cimg_OS'.
+#error (correct values are '0 = unknown OS', '1 = Unix-like OS', '2 = Microsoft Windows').
+#endif
+
+// Disable silly warnings on some Microsoft VC++ compilers.
+#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)
+#define _CRT_SECURE_NO_DEPRECATE 1
+#define _CRT_NONSTDC_NO_DEPRECATE 1
+#endif
+
+// Include OS-specific headers for management of system calls.
+#if cimg_OS==1
+#include <sys/types.h>
+#include <sys/time.h>
+#include <unistd.h>
+#elif cimg_OS==2
+#ifndef NOMINMAX
+#define NOMINMAX
+#endif
+#include <windows.h>
+#ifndef _WIN32_IE
+#define _WIN32_IE 0x0400
+#endif
+#include <shlobj.h>
+#include <process.h>
+#include <io.h>
+#define cimg_snprintf _snprintf
+#define cimg_vsnprintf _vsnprintf
+#endif
+#ifndef cimg_snprintf
+#include <stdio.h>
+#define cimg_snprintf snprintf
+#define cimg_vsnprintf vsnprintf
+#endif
+
+// Configure filename separator.
+//
+// Default separator is '/', except for Windows where it is '\'.
+#ifndef cimg_file_separator
+#if cimg_OS==2
+#define cimg_file_separator '\\'
+#else
+#define cimg_file_separator '/'
+#endif
+#endif
+
+// Configure verbosity of output messages.
+//
+// Define 'cimg_verbosity' to : '0' to hide library messages (quiet mode).
+//                              '1' to output library messages on the console.
+//                              '2' to output library messages on a dialog window (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_verbosity
+#define cimg_verbosity 2
+#elif !(cimg_verbosity==0 || cimg_verbosity==1 || cimg_verbosity==2 || cimg_verbosity==3 || cimg_verbosity==4)
+#error CImg Library : Configuration variable 'cimg_verbosity' is badly defined.
+#error (should be { 0=quiet | 1=console | 2=dialog | 3=console+warnings | 4=dialog+warnings }).
+#endif
+
+// Configure display framework.
+//
+// Define 'cimg_display' to : '0' to disable display capabilities.
+//                            '1' to use the X-Window framework (X11).
+//                            '2' to use the Microsoft GDI32 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)
+#error CImg Library : Configuration variable 'cimg_display' is badly defined.
+#error (should be { 0=none | 1=X-Window (X11) | 2=Microsoft GDI32 }).
+#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
+#endif
+#ifndef cimg_appname
+#define cimg_appname "CImg"
+#endif
+
+// Configure OpenMP support.
+// (http://www.openmp.org)
+//
+// Define 'cimg_use_openmp' to enable OpenMP support.
+//
+// OpenMP directives may be used in a few CImg functions to get
+// advantages of multi-core CPUs.
+#ifdef cimg_use_openmp
+#include "omp.h"
+#define _cimg_static
+#else
+#define _cimg_static static
+#endif
+
+// Configure OpenCV support.
+// (http://opencv.willowgarage.com/wiki/)
+//
+// Define 'cimg_use_opencv' to enable OpenCV support.
+//
+// OpenCV library may be used to access images from cameras
+// (see method 'CImg<T>::load_camera()').
+#ifdef cimg_use_opencv
+#ifdef True
+#undef True
+#define _cimg_redefine_True
+#endif
+#ifdef False
+#undef False
+#define _cimg_redefine_False
+#endif
+#include <cstddef>
+#include "cv.h"
+#include "highgui.h"
+#endif
+
+// Configure LibPNG support.
+// (http://www.libpng.org)
+//
+// Define 'cimg_use_png' to enable LibPNG support.
+//
+// PNG library may be used to get a native support of '.png' files.
+// (see methods 'CImg<T>::{load,save}_png()'.
+#ifdef cimg_use_png
+extern "C" {
+#include "png.h"
+}
+#endif
+
+// Configure LibJPEG support.
+// (http://en.wikipedia.org/wiki/Libjpeg)
+//
+// Define 'cimg_use_jpeg' to enable LibJPEG support.
+//
+// JPEG library may be used to get a native support of '.jpg' files.
+// (see methods 'CImg<T>::{load,save}_jpeg()').
+#ifdef cimg_use_jpeg
+extern "C" {
+#include "jpeglib.h"
+#include "setjmp.h"
+}
+#endif
+
+// Configure LibTIFF support.
+// (http://www.libtiff.org)
+//
+// Define 'cimg_use_tiff' to enable LibTIFF support.
+//
+// TIFF library may be used to get a native support of '.tif' files.
+// (see methods 'CImg[List]<T>::{load,save}_tiff()').
+#ifdef cimg_use_tiff
+extern "C" {
+#include "tiffio.h"
+}
+#endif
+
+// Configure LibMINC2 support.
+// (http://en.wikibooks.org/wiki/MINC/Reference/MINC2.0_File_Format_Reference)
+//
+// Define 'cimg_use_minc2' to enable LibMINC2 support.
+//
+// MINC2 library may be used to get a native support of '.mnc' files.
+// (see methods 'CImg<T>::{load,save}_minc2()').
+#ifdef cimg_use_minc2
+extern "C" {
+#include "minc2.h"
+}
+#endif
+
+// Configure FFMPEG support.
+// (http://www.ffmpeg.org)
+//
+// Define 'cimg_use_ffmpeg' to enable FFMPEG lib support.
+//
+// Avcodec and Avformat libraries from FFMPEG may be used
+// to get a native support of various video file formats.
+// (see methods 'CImg[List]<T>::load_ffmpeg()').
+#ifdef cimg_use_ffmpeg
+#if (defined(_STDINT_H) || defined(_STDINT_H_)) && !defined(UINT64_C)
+#warning "__STDC_CONSTANT_MACROS has to be defined before including <stdint.h>, this file will probably not compile."
+#endif
+#ifndef __STDC_CONSTANT_MACROS
+#define __STDC_CONSTANT_MACROS // ...or stdint.h wont' define UINT64_C, needed by libavutil
+#endif
+extern "C" {
+#include "avformat.h"
+#include "avcodec.h"
+#include "swscale.h"
+}
+#endif
+
+// Configure Zlib support.
+// (http://www.zlib.net)
+//
+// Define 'cimg_use_zlib' to enable Zlib support.
+//
+// Zlib library may be used to allow compressed data in '.cimgz' files
+// (see methods 'CImg[List]<T>::{load,save}_cimg()').
+#ifdef cimg_use_zlib
+extern "C" {
+#include "zlib.h"
+}
+#endif
+
+// Configure Magick++ support.
+// (http://www.imagemagick.org/Magick++)
+//
+// Define 'cimg_use_magick' to enable Magick++ support.
+//
+// Magick++ library may be used to get a native support of various image file formats.
+// (see methods 'CImg<T>::{load,save}()').
+#ifdef cimg_use_magick
+#include "Magick++.h"
+#endif
+
+// Configure FFTW3 support.
+// (http://www.fftw.org)
+//
+// Define 'cimg_use_fftw3' to enable libFFTW3 support.
+//
+// FFTW3 library may be used to efficiently compute the Fast Fourier Transform
+// of image data, without restriction on the image size.
+// (see method 'CImg[List]<T>::FFT()').
+#ifdef cimg_use_fftw3
+extern "C" {
+#include "fftw3.h"
+}
+#endif
+
+// Configure LibBoard support.
+// (http://libboard.sourceforge.net/)
+//
+// Define 'cimg_use_board' to enable Board support.
+//
+// Board library may be used to draw 3d objects in vector-graphics canvas
+// that can be saved as '.ps' or '.svg' files afterwards.
+// (see method 'CImg<T>::draw_object3d()').
+#ifdef cimg_use_board
+#ifdef None
+#undef None
+#define _cimg_redefine_None
+#endif
+#include "Board.h"
+#endif
+
+// Configure OpenEXR support.
+// (http://www.openexr.com/)
+//
+// Define 'cimg_use_openexr' to enable OpenEXR support.
+//
+// OpenEXR library may be used to get a native support of '.exr' files.
+// (see methods 'CImg<T>::{load,save}_exr()').
+#ifdef cimg_use_openexr
+#include "ImfRgbaFile.h"
+#include "ImfInputFile.h"
+#include "ImfChannelList.h"
+#include "ImfMatrixAttribute.h"
+#include "ImfArray.h"
+#endif
+
+// Lapack configuration.
+// (http://www.netlib.org/lapack)
+//
+// Define 'cimg_use_lapack' to enable LAPACK support.
+//
+// Lapack library may be used in several CImg methods to speed up
+// matrix computations (eigenvalues, inverse, ...).
+#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/PI macros are defined.
+//
+// CImg does not compile if macros 'min', 'max' or 'PI' are defined,
+// because it redefines functions min(), max() and const variable PI in the cimg:: namespace.
+// so it '#undef' these macros if necessary, and restore them to reasonable
+// values at the end of this file.
+#ifdef min
+#undef min
+#define _cimg_redefine_min
+#endif
+#ifdef max
+#undef max
+#define _cimg_redefine_max
+#endif
+#ifdef PI
+#undef PI
+#define _cimg_redefine_PI
+#endif
+
+// Define 'cimg_library' namespace suffix.
+//
+// You may want to add a suffix to the 'cimg_library' namespace, for instance if you need to work
+// with several versions of the library at the same time.
+#ifdef cimg_namespace_suffix
+#define __cimg_library_suffixed(s) cimg_library_##s
+#define _cimg_library_suffixed(s) __cimg_library_suffixed(s)
+#define cimg_library_suffixed _cimg_library_suffixed(cimg_namespace_suffix)
+#else
+#define cimg_library_suffixed cimg_library
+#endif
+
+/*------------------------------------------------------------------------------
+  #
+  # Define user-friendly macros.
+  #
+  # These CImg macros are prefixed by 'cimg_' and can be used safely in your own
+  # code. They are useful for option parsing, or writing specific image loops.
+  #
+  ------------------------------------------------------------------------------*/
+
+// Macros to define program usage, and retrieve command line arguments.
+#define cimg_usage(usage) cimg_library_suffixed::cimg::option((char*)0,argc,argv,(char*)0,usage,false)
+#define cimg_help(str) cimg_library_suffixed::cimg::option((char*)0,argc,argv,str,(char*)0)
+#define cimg_option(name,defaut,usage) cimg_library_suffixed::cimg::option(name,argc,argv,defaut,usage)
+#define cimg_argument(pos) cimg_library_suffixed::cimg::argument(pos,argc,argv)
+#define cimg_argument1(pos,s0) cimg_library_suffixed::cimg::argument(pos,argc,argv,1,s0)
+#define cimg_argument2(pos,s0,s1) cimg_library_suffixed::cimg::argument(pos,argc,argv,2,s0,s1)
+#define cimg_argument3(pos,s0,s1,s2) cimg_library_suffixed::cimg::argument(pos,argc,argv,3,s0,s1,s2)
+#define cimg_argument4(pos,s0,s1,s2,s3) cimg_library_suffixed::cimg::argument(pos,argc,argv,4,s0,s1,s2,s3)
+#define cimg_argument5(pos,s0,s1,s2,s3,s4) cimg_library_suffixed::cimg::argument(pos,argc,argv,5,s0,s1,s2,s3,s4)
+#define cimg_argument6(pos,s0,s1,s2,s3,s4,s5) cimg_library_suffixed::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_suffixed::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_suffixed::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_suffixed::cimg::argument(pos,argc,argv,9,s0,s1,s2,s3,s4,s5,s6,s7,s8)
+
+// Macros to 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,c,I,T) \
+  I[0] = (T)(img)(x,y,z,c), I[1] = (T)(img)(_n1##x,y,z,c), I[2] = (T)(img)(x,_n1##y,z,c), I[3] = (T)(img)(_n1##x,_n1##y,z,c)
+
+#define cimg_get3x3(img,x,y,z,c,I,T) \
+  I[0] = (T)(img)(_p1##x,_p1##y,z,c), I[1] = (T)(img)(x,_p1##y,z,c), I[2] = (T)(img)(_n1##x,_p1##y,z,c), I[3] = (T)(img)(_p1##x,y,z,c), \
+  I[4] = (T)(img)(x,y,z,c), I[5] = (T)(img)(_n1##x,y,z,c), I[6] = (T)(img)(_p1##x,_n1##y,z,c), I[7] = (T)(img)(x,_n1##y,z,c), \
+  I[8] = (T)(img)(_n1##x,_n1##y,z,c)
+
+#define cimg_get4x4(img,x,y,z,c,I,T) \
+  I[0] = (T)(img)(_p1##x,_p1##y,z,c), I[1] = (T)(img)(x,_p1##y,z,c), I[2] = (T)(img)(_n1##x,_p1##y,z,c), I[3] = (T)(img)(_n2##x,_p1##y,z,c), \
+  I[4] = (T)(img)(_p1##x,y,z,c), I[5] = (T)(img)(x,y,z,c), I[6] = (T)(img)(_n1##x,y,z,c), I[7] = (T)(img)(_n2##x,y,z,c), \
+  I[8] = (T)(img)(_p1##x,_n1##y,z,c), I[9] = (T)(img)(x,_n1##y,z,c), I[10] = (T)(img)(_n1##x,_n1##y,z,c), I[11] = (T)(img)(_n2##x,_n1##y,z,c), \
+  I[12] = (T)(img)(_p1##x,_n2##y,z,c), I[13] = (T)(img)(x,_n2##y,z,c), I[14] = (T)(img)(_n1##x,_n2##y,z,c), I[15] = (T)(img)(_n2##x,_n2##y,z,c)
+
+#define cimg_get5x5(img,x,y,z,c,I,T) \
+  I[0] = (T)(img)(_p2##x,_p2##y,z,c), I[1] = (T)(img)(_p1##x,_p2##y,z,c), I[2] = (T)(img)(x,_p2##y,z,c), I[3] = (T)(img)(_n1##x,_p2##y,z,c), \
+  I[4] = (T)(img)(_n2##x,_p2##y,z,c), I[5] = (T)(img)(_p2##x,_p1##y,z,c), I[6] = (T)(img)(_p1##x,_p1##y,z,c), I[7] = (T)(img)(x,_p1##y,z,c), \
+  I[8] = (T)(img)(_n1##x,_p1##y,z,c), I[9] = (T)(img)(_n2##x,_p1##y,z,c), I[10] = (T)(img)(_p2##x,y,z,c), I[11] = (T)(img)(_p1##x,y,z,c), \
+  I[12] = (T)(img)(x,y,z,c), I[13] = (T)(img)(_n1##x,y,z,c), I[14] = (T)(img)(_n2##x,y,z,c), I[15] = (T)(img)(_p2##x,_n1##y,z,c), \
+  I[16] = (T)(img)(_p1##x,_n1##y,z,c), I[17] = (T)(img)(x,_n1##y,z,c), I[18] = (T)(img)(_n1##x,_n1##y,z,c), I[19] = (T)(img)(_n2##x,_n1##y,z,c), \
+  I[20] = (T)(img)(_p2##x,_n2##y,z,c), I[21] = (T)(img)(_p1##x,_n2##y,z,c), I[22] = (T)(img)(x,_n2##y,z,c), I[23] = (T)(img)(_n1##x,_n2##y,z,c), \
+  I[24] = (T)(img)(_n2##x,_n2##y,z,c)
+
+#define cimg_get6x6(img,x,y,z,c,I,T) \
+ I[0] = (T)(img)(_p2##x,_p2##y,z,c), I[1] = (T)(img)(_p1##x,_p2##y,z,c), I[2] = (T)(img)(x,_p2##y,z,c), I[3] = (T)(img)(_n1##x,_p2##y,z,c), \
+ I[4] = (T)(img)(_n2##x,_p2##y,z,c), I[5] = (T)(img)(_n3##x,_p2##y,z,c), I[6] = (T)(img)(_p2##x,_p1##y,z,c), I[7] = (T)(img)(_p1##x,_p1##y,z,c), \
+ I[8] = (T)(img)(x,_p1##y,z,c), I[9] = (T)(img)(_n1##x,_p1##y,z,c), I[10] = (T)(img)(_n2##x,_p1##y,z,c), I[11] = (T)(img)(_n3##x,_p1##y,z,c), \
+ I[12] = (T)(img)(_p2##x,y,z,c), I[13] = (T)(img)(_p1##x,y,z,c), I[14] = (T)(img)(x,y,z,c), I[15] = (T)(img)(_n1##x,y,z,c), \
+ I[16] = (T)(img)(_n2##x,y,z,c), I[17] = (T)(img)(_n3##x,y,z,c), I[18] = (T)(img)(_p2##x,_n1##y,z,c), I[19] = (T)(img)(_p1##x,_n1##y,z,c), \
+ I[20] = (T)(img)(x,_n1##y,z,c), I[21] = (T)(img)(_n1##x,_n1##y,z,c), I[22] = (T)(img)(_n2##x,_n1##y,z,c), I[23] = (T)(img)(_n3##x,_n1##y,z,c), \
+ I[24] = (T)(img)(_p2##x,_n2##y,z,c), I[25] = (T)(img)(_p1##x,_n2##y,z,c), I[26] = (T)(img)(x,_n2##y,z,c), I[27] = (T)(img)(_n1##x,_n2##y,z,c), \
+ I[28] = (T)(img)(_n2##x,_n2##y,z,c), I[29] = (T)(img)(_n3##x,_n2##y,z,c), I[30] = (T)(img)(_p2##x,_n3##y,z,c), I[31] = (T)(img)(_p1##x,_n3##y,z,c), \
+ I[32] = (T)(img)(x,_n3##y,z,c), I[33] = (T)(img)(_n1##x,_n3##y,z,c), I[34] = (T)(img)(_n2##x,_n3##y,z,c), I[35] = (T)(img)(_n3##x,_n3##y,z,c)
+
+#define cimg_get7x7(img,x,y,z,c,I,T) \
+ I[0] = (T)(img)(_p3##x,_p3##y,z,c), I[1] = (T)(img)(_p2##x,_p3##y,z,c), I[2] = (T)(img)(_p1##x,_p3##y,z,c), I[3] = (T)(img)(x,_p3##y,z,c), \
+ I[4] = (T)(img)(_n1##x,_p3##y,z,c), I[5] = (T)(img)(_n2##x,_p3##y,z,c), I[6] = (T)(img)(_n3##x,_p3##y,z,c), I[7] = (T)(img)(_p3##x,_p2##y,z,c), \
+ I[8] = (T)(img)(_p2##x,_p2##y,z,c), I[9] = (T)(img)(_p1##x,_p2##y,z,c), I[10] = (T)(img)(x,_p2##y,z,c), I[11] = (T)(img)(_n1##x,_p2##y,z,c), \
+ I[12] = (T)(img)(_n2##x,_p2##y,z,c), I[13] = (T)(img)(_n3##x,_p2##y,z,c), I[14] = (T)(img)(_p3##x,_p1##y,z,c), I[15] = (T)(img)(_p2##x,_p1##y,z,c), \
+ I[16] = (T)(img)(_p1##x,_p1##y,z,c), I[17] = (T)(img)(x,_p1##y,z,c), I[18] = (T)(img)(_n1##x,_p1##y,z,c), I[19] = (T)(img)(_n2##x,_p1##y,z,c), \
+ I[20] = (T)(img)(_n3##x,_p1##y,z,c), I[21] = (T)(img)(_p3##x,y,z,c), I[22] = (T)(img)(_p2##x,y,z,c), I[23] = (T)(img)(_p1##x,y,z,c), \
+ I[24] = (T)(img)(x,y,z,c), I[25] = (T)(img)(_n1##x,y,z,c), I[26] = (T)(img)(_n2##x,y,z,c), I[27] = (T)(img)(_n3##x,y,z,c), \
+ I[28] = (T)(img)(_p3##x,_n1##y,z,c), I[29] = (T)(img)(_p2##x,_n1##y,z,c), I[30] = (T)(img)(_p1##x,_n1##y,z,c), I[31] = (T)(img)(x,_n1##y,z,c), \
+ I[32] = (T)(img)(_n1##x,_n1##y,z,c), I[33] = (T)(img)(_n2##x,_n1##y,z,c), I[34] = (T)(img)(_n3##x,_n1##y,z,c), I[35] = (T)(img)(_p3##x,_n2##y,z,c), \
+ I[36] = (T)(img)(_p2##x,_n2##y,z,c), I[37] = (T)(img)(_p1##x,_n2##y,z,c), I[38] = (T)(img)(x,_n2##y,z,c), I[39] = (T)(img)(_n1##x,_n2##y,z,c), \
+ I[40] = (T)(img)(_n2##x,_n2##y,z,c), I[41] = (T)(img)(_n3##x,_n2##y,z,c), I[42] = (T)(img)(_p3##x,_n3##y,z,c), I[43] = (T)(img)(_p2##x,_n3##y,z,c), \
+ I[44] = (T)(img)(_p1##x,_n3##y,z,c), I[45] = (T)(img)(x,_n3##y,z,c), I[46] = (T)(img)(_n1##x,_n3##y,z,c), I[47] = (T)(img)(_n2##x,_n3##y,z,c), \
+ I[48] = (T)(img)(_n3##x,_n3##y,z,c)
+
+#define cimg_get8x8(img,x,y,z,c,I,T) \
+ I[0] = (T)(img)(_p3##x,_p3##y,z,c), I[1] = (T)(img)(_p2##x,_p3##y,z,c), I[2] = (T)(img)(_p1##x,_p3##y,z,c), I[3] = (T)(img)(x,_p3##y,z,c), \
+ I[4] = (T)(img)(_n1##x,_p3##y,z,c), I[5] = (T)(img)(_n2##x,_p3##y,z,c), I[6] = (T)(img)(_n3##x,_p3##y,z,c), I[7] = (T)(img)(_n4##x,_p3##y,z,c), \
+ I[8] = (T)(img)(_p3##x,_p2##y,z,c), I[9] = (T)(img)(_p2##x,_p2##y,z,c), I[10] = (T)(img)(_p1##x,_p2##y,z,c), I[11] = (T)(img)(x,_p2##y,z,c), \
+ I[12] = (T)(img)(_n1##x,_p2##y,z,c), I[13] = (T)(img)(_n2##x,_p2##y,z,c), I[14] = (T)(img)(_n3##x,_p2##y,z,c), I[15] = (T)(img)(_n4##x,_p2##y,z,c), \
+ I[16] = (T)(img)(_p3##x,_p1##y,z,c), I[17] = (T)(img)(_p2##x,_p1##y,z,c), I[18] = (T)(img)(_p1##x,_p1##y,z,c), I[19] = (T)(img)(x,_p1##y,z,c), \
+ I[20] = (T)(img)(_n1##x,_p1##y,z,c), I[21] = (T)(img)(_n2##x,_p1##y,z,c), I[22] = (T)(img)(_n3##x,_p1##y,z,c), I[23] = (T)(img)(_n4##x,_p1##y,z,c), \
+ I[24] = (T)(img)(_p3##x,y,z,c), I[25] = (T)(img)(_p2##x,y,z,c), I[26] = (T)(img)(_p1##x,y,z,c), I[27] = (T)(img)(x,y,z,c), \
+ I[28] = (T)(img)(_n1##x,y,z,c), I[29] = (T)(img)(_n2##x,y,z,c), I[30] = (T)(img)(_n3##x,y,z,c), I[31] = (T)(img)(_n4##x,y,z,c), \
+ I[32] = (T)(img)(_p3##x,_n1##y,z,c), I[33] = (T)(img)(_p2##x,_n1##y,z,c), I[34] = (T)(img)(_p1##x,_n1##y,z,c), I[35] = (T)(img)(x,_n1##y,z,c), \
+ I[36] = (T)(img)(_n1##x,_n1##y,z,c), I[37] = (T)(img)(_n2##x,_n1##y,z,c), I[38] = (T)(img)(_n3##x,_n1##y,z,c), I[39] = (T)(img)(_n4##x,_n1##y,z,c), \
+ I[40] = (T)(img)(_p3##x,_n2##y,z,c), I[41] = (T)(img)(_p2##x,_n2##y,z,c), I[42] = (T)(img)(_p1##x,_n2##y,z,c), I[43] = (T)(img)(x,_n2##y,z,c), \
+ I[44] = (T)(img)(_n1##x,_n2##y,z,c), I[45] = (T)(img)(_n2##x,_n2##y,z,c), I[46] = (T)(img)(_n3##x,_n2##y,z,c), I[47] = (T)(img)(_n4##x,_n2##y,z,c), \
+ I[48] = (T)(img)(_p3##x,_n3##y,z,c), I[49] = (T)(img)(_p2##x,_n3##y,z,c), I[50] = (T)(img)(_p1##x,_n3##y,z,c), I[51] = (T)(img)(x,_n3##y,z,c), \
+ I[52] = (T)(img)(_n1##x,_n3##y,z,c), I[53] = (T)(img)(_n2##x,_n3##y,z,c), I[54] = (T)(img)(_n3##x,_n3##y,z,c), I[55] = (T)(img)(_n4##x,_n3##y,z,c), \
+ I[56] = (T)(img)(_p3##x,_n4##y,z,c), I[57] = (T)(img)(_p2##x,_n4##y,z,c), I[58] = (T)(img)(_p1##x,_n4##y,z,c), I[59] = (T)(img)(x,_n4##y,z,c), \
+ I[60] = (T)(img)(_n1##x,_n4##y,z,c), I[61] = (T)(img)(_n2##x,_n4##y,z,c), I[62] = (T)(img)(_n3##x,_n4##y,z,c), I[63] = (T)(img)(_n4##x,_n4##y,z,c);
+
+#define cimg_get9x9(img,x,y,z,c,I,T) \
+ I[0] = (T)(img)(_p4##x,_p4##y,z,c), I[1] = (T)(img)(_p3##x,_p4##y,z,c), I[2] = (T)(img)(_p2##x,_p4##y,z,c), I[3] = (T)(img)(_p1##x,_p4##y,z,c), \
+ I[4] = (T)(img)(x,_p4##y,z,c), I[5] = (T)(img)(_n1##x,_p4##y,z,c), I[6] = (T)(img)(_n2##x,_p4##y,z,c), I[7] = (T)(img)(_n3##x,_p4##y,z,c), \
+ I[8] = (T)(img)(_n4##x,_p4##y,z,c), I[9] = (T)(img)(_p4##x,_p3##y,z,c), I[10] = (T)(img)(_p3##x,_p3##y,z,c), I[11] = (T)(img)(_p2##x,_p3##y,z,c), \
+ I[12] = (T)(img)(_p1##x,_p3##y,z,c), I[13] = (T)(img)(x,_p3##y,z,c), I[14] = (T)(img)(_n1##x,_p3##y,z,c), I[15] = (T)(img)(_n2##x,_p3##y,z,c), \
+ I[16] = (T)(img)(_n3##x,_p3##y,z,c), I[17] = (T)(img)(_n4##x,_p3##y,z,c), I[18] = (T)(img)(_p4##x,_p2##y,z,c), I[19] = (T)(img)(_p3##x,_p2##y,z,c), \
+ I[20] = (T)(img)(_p2##x,_p2##y,z,c), I[21] = (T)(img)(_p1##x,_p2##y,z,c), I[22] = (T)(img)(x,_p2##y,z,c), I[23] = (T)(img)(_n1##x,_p2##y,z,c), \
+ I[24] = (T)(img)(_n2##x,_p2##y,z,c), I[25] = (T)(img)(_n3##x,_p2##y,z,c), I[26] = (T)(img)(_n4##x,_p2##y,z,c), I[27] = (T)(img)(_p4##x,_p1##y,z,c), \
+ I[28] = (T)(img)(_p3##x,_p1##y,z,c), I[29] = (T)(img)(_p2##x,_p1##y,z,c), I[30] = (T)(img)(_p1##x,_p1##y,z,c), I[31] = (T)(img)(x,_p1##y,z,c), \
+ I[32] = (T)(img)(_n1##x,_p1##y,z,c), I[33] = (T)(img)(_n2##x,_p1##y,z,c), I[34] = (T)(img)(_n3##x,_p1##y,z,c), I[35] = (T)(img)(_n4##x,_p1##y,z,c), \
+ I[36] = (T)(img)(_p4##x,y,z,c), I[37] = (T)(img)(_p3##x,y,z,c), I[38] = (T)(img)(_p2##x,y,z,c), I[39] = (T)(img)(_p1##x,y,z,c), \
+ I[40] = (T)(img)(x,y,z,c), I[41] = (T)(img)(_n1##x,y,z,c), I[42] = (T)(img)(_n2##x,y,z,c), I[43] = (T)(img)(_n3##x,y,z,c), \
+ I[44] = (T)(img)(_n4##x,y,z,c), I[45] = (T)(img)(_p4##x,_n1##y,z,c), I[46] = (T)(img)(_p3##x,_n1##y,z,c), I[47] = (T)(img)(_p2##x,_n1##y,z,c), \
+ I[48] = (T)(img)(_p1##x,_n1##y,z,c), I[49] = (T)(img)(x,_n1##y,z,c), I[50] = (T)(img)(_n1##x,_n1##y,z,c), I[51] = (T)(img)(_n2##x,_n1##y,z,c), \
+ I[52] = (T)(img)(_n3##x,_n1##y,z,c), I[53] = (T)(img)(_n4##x,_n1##y,z,c), I[54] = (T)(img)(_p4##x,_n2##y,z,c), I[55] = (T)(img)(_p3##x,_n2##y,z,c), \
+ I[56] = (T)(img)(_p2##x,_n2##y,z,c), I[57] = (T)(img)(_p1##x,_n2##y,z,c), I[58] = (T)(img)(x,_n2##y,z,c), I[59] = (T)(img)(_n1##x,_n2##y,z,c), \
+ I[60] = (T)(img)(_n2##x,_n2##y,z,c), I[61] = (T)(img)(_n3##x,_n2##y,z,c), I[62] = (T)(img)(_n4##x,_n2##y,z,c), I[63] = (T)(img)(_p4##x,_n3##y,z,c), \
+ I[64] = (T)(img)(_p3##x,_n3##y,z,c), I[65] = (T)(img)(_p2##x,_n3##y,z,c), I[66] = (T)(img)(_p1##x,_n3##y,z,c), I[67] = (T)(img)(x,_n3##y,z,c), \
+ I[68] = (T)(img)(_n1##x,_n3##y,z,c), I[69] = (T)(img)(_n2##x,_n3##y,z,c), I[70] = (T)(img)(_n3##x,_n3##y,z,c), I[71] = (T)(img)(_n4##x,_n3##y,z,c), \
+ I[72] = (T)(img)(_p4##x,_n4##y,z,c), I[73] = (T)(img)(_p3##x,_n4##y,z,c), I[74] = (T)(img)(_p2##x,_n4##y,z,c), I[75] = (T)(img)(_p1##x,_n4##y,z,c), \
+ I[76] = (T)(img)(x,_n4##y,z,c), I[77] = (T)(img)(_n1##x,_n4##y,z,c), I[78] = (T)(img)(_n2##x,_n4##y,z,c), I[79] = (T)(img)(_n3##x,_n4##y,z,c), \
+ I[80] = (T)(img)(_n4##x,_n4##y,z,c)
+
+#define cimg_get2x2x2(img,x,y,z,c,I,T) \
+  I[0] = (T)(img)(x,y,z,c), I[1] = (T)(img)(_n1##x,y,z,c), I[2] = (T)(img)(x,_n1##y,z,c), I[3] = (T)(img)(_n1##x,_n1##y,z,c), \
+  I[4] = (T)(img)(x,y,_n1##z,c), I[5] = (T)(img)(_n1##x,y,_n1##z,c), I[6] = (T)(img)(x,_n1##y,_n1##z,c), I[7] = (T)(img)(_n1##x,_n1##y,_n1##z,c)
+
+#define cimg_get3x3x3(img,x,y,z,c,I,T) \
+  I[0] = (T)(img)(_p1##x,_p1##y,_p1##z,c), I[1] = (T)(img)(x,_p1##y,_p1##z,c), I[2] = (T)(img)(_n1##x,_p1##y,_p1##z,c), \
+  I[3] = (T)(img)(_p1##x,y,_p1##z,c), I[4] = (T)(img)(x,y,_p1##z,c), I[5] = (T)(img)(_n1##x,y,_p1##z,c), \
+  I[6] = (T)(img)(_p1##x,_n1##y,_p1##z,c), I[7] = (T)(img)(x,_n1##y,_p1##z,c), I[8] = (T)(img)(_n1##x,_n1##y,_p1##z,c), \
+  I[9] = (T)(img)(_p1##x,_p1##y,z,c), I[10] = (T)(img)(x,_p1##y,z,c), I[11] = (T)(img)(_n1##x,_p1##y,z,c), \
+  I[12] = (T)(img)(_p1##x,y,z,c), I[13] = (T)(img)(x,y,z,c), I[14] = (T)(img)(_n1##x,y,z,c), \
+  I[15] = (T)(img)(_p1##x,_n1##y,z,c), I[16] = (T)(img)(x,_n1##y,z,c), I[17] = (T)(img)(_n1##x,_n1##y,z,c), \
+  I[18] = (T)(img)(_p1##x,_p1##y,_n1##z,c), I[19] = (T)(img)(x,_p1##y,_n1##z,c), I[20] = (T)(img)(_n1##x,_p1##y,_n1##z,c), \
+  I[21] = (T)(img)(_p1##x,y,_n1##z,c), I[22] = (T)(img)(x,y,_n1##z,c), I[23] = (T)(img)(_n1##x,y,_n1##z,c), \
+  I[24] = (T)(img)(_p1##x,_n1##y,_n1##z,c), I[25] = (T)(img)(x,_n1##y,_n1##z,c), I[26] = (T)(img)(_n1##x,_n1##y,_n1##z,c)
+
+// Macros to perform various image loops.
+//
+// These macros may prevent the use of iterators, if necessary.
+#define cimg_for(img,ptrs,T_ptrs) for (T_ptrs *ptrs = (img)._data + (img).size(); (ptrs--)>(img)._data; )
+#define cimg_forptr(img,ptrs,T_ptrs) for (T_ptrs *ptrs = (img)._data, *_max##ptrs = (img)._data + (img).size(); ptrs<_max##ptrs; ++ptrs)
+#define cimg_foroff(img,off) for (unsigned int off = 0, _max##off = (img).size(); off<_max##off; ++off)
+
+#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_forC(img,c) cimg_for1((img)._spectrum,c)
+#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_forXC(img,x,c) cimg_forC(img,c) cimg_forX(img,x)
+#define cimg_forYC(img,y,c) cimg_forC(img,c) cimg_forY(img,y)
+#define cimg_forZC(img,z,c) cimg_forC(img,c) cimg_forZ(img,z)
+#define cimg_forXYZ(img,x,y,z) cimg_forZ(img,z) cimg_forXY(img,x,y)
+#define cimg_forXYC(img,x,y,c) cimg_forC(img,c) cimg_forXY(img,x,y)
+#define cimg_forXZC(img,x,z,c) cimg_forC(img,c) cimg_forXZ(img,x,z)
+#define cimg_forYZC(img,y,z,c) cimg_forC(img,c) cimg_forYZ(img,y,z)
+#define cimg_forXYZC(img,x,y,z,c) cimg_forC(img,c) 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_inC(img,c0,c1,c) cimg_for_in1((img)._spectrum,c0,c1,c)
+#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_inXC(img,x0,c0,x1,c1,x,c) cimg_for_inC(img,c0,c1,c) 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_inYC(img,y0,c0,y1,c1,y,c) cimg_for_inC(img,c0,c1,c) cimg_for_inY(img,y0,y1,y)
+#define cimg_for_inZC(img,z0,c0,z1,c1,z,c) cimg_for_inC(img,c0,c1,c) 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_inXYC(img,x0,y0,c0,x1,y1,c1,x,y,c) cimg_for_inC(img,c0,c1,c) cimg_for_inXY(img,x0,y0,x1,y1,x,y)
+#define cimg_for_inXZC(img,x0,z0,c0,x1,z1,c1,x,z,c) cimg_for_inC(img,c0,c1,c) cimg_for_inXZ(img,x0,z0,x1,z1,x,z)
+#define cimg_for_inYZC(img,y0,z0,c0,y1,z1,c1,y,z,c) cimg_for_inC(img,c0,c1,c) cimg_for_inYZ(img,y0,z0,y1,z1,y,z)
+#define cimg_for_inXYZC(img,x0,y0,z0,c0,x1,y1,z1,c1,x,y,z,c) cimg_for_inC(img,c0,c1,c) 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_insideC(img,c,n) cimg_for_inC(img,n,(img)._spectrum-1-(n),c)
+#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_insideXYZC(img,x,y,z,c,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_outC(img,c0,c1,c) cimg_for_out1((img)._spectrum,c0,c1,c)
+#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_outXC(img,x0,c0,x1,c1,x,c) cimg_for_out2((img)._width,(img)._spectrum,x0,c0,x1,c1,x,c)
+#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_outYC(img,y0,c0,y1,c1,y,c) cimg_for_out2((img)._height,(img)._spectrum,y0,c0,y1,c1,y,c)
+#define cimg_for_outZC(img,z0,c0,z1,c1,z,c) cimg_for_out2((img)._depth,(img)._spectrum,z0,c0,z1,c1,z,c)
+#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_outXYC(img,x0,y0,c0,x1,y1,c1,x,y,c) cimg_for_out3((img)._width,(img)._height,(img)._spectrum,x0,y0,c0,x1,y1,c1,x,y,c)
+#define cimg_for_outXZC(img,x0,z0,c0,x1,z1,c1,x,z,c) cimg_for_out3((img)._width,(img)._depth,(img)._spectrum,x0,z0,c0,x1,z1,c1,x,z,c)
+#define cimg_for_outYZC(img,y0,z0,c0,y1,z1,c1,y,z,c) cimg_for_out3((img)._height,(img)._depth,(img)._spectrum,y0,z0,c0,y1,z1,c1,y,z,c)
+#define cimg_for_outXYZC(img,x0,y0,z0,c0,x1,y1,z1,c1,x,y,z,c) \
+ cimg_for_out4((img)._width,(img)._height,(img)._depth,(img)._spectrum,x0,y0,z0,c0,x1,y1,z1,c1,x,y,z,c)
+#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_borderC(img,c,n) cimg_for_outC(img,n,(img)._spectrum-1-(n),c)
+#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_borderXYZC(img,x,y,z,c,n) \
+ cimg_for_outXYZC(img,n,n,n,n,(img)._width-1-(n),(img)._height-1-(n),(img)._depth-1-(n),(img)._spectrum-1-(n),x,y,z,c)
+
+#define cimg_for_spiralXY(img,x,y) \
+ for (int x = 0, y = 0, _n1##x = 1, _n1##y = (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_for2C(img,c) cimg_for2((img)._spectrum,c)
+#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_for2XC(img,x,c) cimg_for2C(img,c) cimg_for2X(img,x)
+#define cimg_for2YZ(img,y,z) cimg_for2Z(img,z) cimg_for2Y(img,y)
+#define cimg_for2YC(img,y,c) cimg_for2C(img,c) cimg_for2Y(img,y)
+#define cimg_for2ZC(img,z,c) cimg_for2C(img,c) cimg_for2Z(img,z)
+#define cimg_for2XYZ(img,x,y,z) cimg_for2Z(img,z) cimg_for2XY(img,x,y)
+#define cimg_for2XZC(img,x,z,c) cimg_for2C(img,c) cimg_for2XZ(img,x,z)
+#define cimg_for2YZC(img,y,z,c) cimg_for2C(img,c) cimg_for2YZ(img,y,z)
+#define cimg_for2XYZC(img,x,y,z,c) cimg_for2C(img,c) 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_in2C(img,c0,c1,c) cimg_for_in2((img)._spectrum,c0,c1,c)
+#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_in2XC(img,x0,c0,x1,c1,x,c) cimg_for_in2C(img,c0,c1,c) 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_in2YC(img,y0,c0,y1,c1,y,c) cimg_for_in2C(img,c0,c1,c) cimg_for_in2Y(img,y0,y1,y)
+#define cimg_for_in2ZC(img,z0,c0,z1,c1,z,c) cimg_for_in2C(img,c0,c1,c) 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_in2XZC(img,x0,z0,c0,x1,y1,c1,x,z,c) cimg_for_in2C(img,c0,c1,c) cimg_for_in2XZ(img,x0,y0,x1,y1,x,z)
+#define cimg_for_in2YZC(img,y0,z0,c0,y1,z1,c1,y,z,c) cimg_for_in2C(img,c0,c1,c) cimg_for_in2YZ(img,y0,z0,y1,z1,y,z)
+#define cimg_for_in2XYZC(img,x0,y0,z0,c0,x1,y1,z1,c1,x,y,z,c) cimg_for_in2C(img,c0,c1,c) 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_for3C(img,c) cimg_for3((img)._spectrum,c)
+#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_for3XC(img,x,c) cimg_for3C(img,c) cimg_for3X(img,x)
+#define cimg_for3YZ(img,y,z) cimg_for3Z(img,z) cimg_for3Y(img,y)
+#define cimg_for3YC(img,y,c) cimg_for3C(img,c) cimg_for3Y(img,y)
+#define cimg_for3ZC(img,z,c) cimg_for3C(img,c) cimg_for3Z(img,z)
+#define cimg_for3XYZ(img,x,y,z) cimg_for3Z(img,z) cimg_for3XY(img,x,y)
+#define cimg_for3XZC(img,x,z,c) cimg_for3C(img,c) cimg_for3XZ(img,x,z)
+#define cimg_for3YZC(img,y,z,c) cimg_for3C(img,c) cimg_for3YZ(img,y,z)
+#define cimg_for3XYZC(img,x,y,z,c) cimg_for3C(img,c) 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_in3C(img,c0,c1,c) cimg_for_in3((img)._spectrum,c0,c1,c)
+#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_in3XC(img,x0,c0,x1,c1,x,c) cimg_for_in3C(img,c0,c1,c) 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_in3YC(img,y0,c0,y1,c1,y,c) cimg_for_in3C(img,c0,c1,c) cimg_for_in3Y(img,y0,y1,y)
+#define cimg_for_in3ZC(img,z0,c0,z1,c1,z,c) cimg_for_in3C(img,c0,c1,c) 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_in3XZC(img,x0,z0,c0,x1,y1,c1,x,z,c) cimg_for_in3C(img,c0,c1,c) cimg_for_in3XZ(img,x0,y0,x1,y1,x,z)
+#define cimg_for_in3YZC(img,y0,z0,c0,y1,z1,c1,y,z,c) cimg_for_in3C(img,c0,c1,c) cimg_for_in3YZ(img,y0,z0,y1,z1,y,z)
+#define cimg_for_in3XYZC(img,x0,y0,z0,c0,x1,y1,z1,c1,x,y,z,c) cimg_for_in3C(img,c0,c1,c) 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_for4C(img,c) cimg_for4((img)._spectrum,c)
+#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_for4XC(img,x,c) cimg_for4C(img,c) cimg_for4X(img,x)
+#define cimg_for4YZ(img,y,z) cimg_for4Z(img,z) cimg_for4Y(img,y)
+#define cimg_for4YC(img,y,c) cimg_for4C(img,c) cimg_for4Y(img,y)
+#define cimg_for4ZC(img,z,c) cimg_for4C(img,c) cimg_for4Z(img,z)
+#define cimg_for4XYZ(img,x,y,z) cimg_for4Z(img,z) cimg_for4XY(img,x,y)
+#define cimg_for4XZC(img,x,z,c) cimg_for4C(img,c) cimg_for4XZ(img,x,z)
+#define cimg_for4YZC(img,y,z,c) cimg_for4C(img,c) cimg_for4YZ(img,y,z)
+#define cimg_for4XYZC(img,x,y,z,c) cimg_for4C(img,c) 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_in4C(img,c0,c1,c) cimg_for_in4((img)._spectrum,c0,c1,c)
+#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_in4XC(img,x0,c0,x1,c1,x,c) cimg_for_in4C(img,c0,c1,c) 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_in4YC(img,y0,c0,y1,c1,y,c) cimg_for_in4C(img,c0,c1,c) cimg_for_in4Y(img,y0,y1,y)
+#define cimg_for_in4ZC(img,z0,c0,z1,c1,z,c) cimg_for_in4C(img,c0,c1,c) 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_in4XZC(img,x0,z0,c0,x1,y1,c1,x,z,c) cimg_for_in4C(img,c0,c1,c) cimg_for_in4XZ(img,x0,y0,x1,y1,x,z)
+#define cimg_for_in4YZC(img,y0,z0,c0,y1,z1,c1,y,z,c) cimg_for_in4C(img,c0,c1,c) cimg_for_in4YZ(img,y0,z0,y1,z1,y,z)
+#define cimg_for_in4XYZC(img,x0,y0,z0,c0,x1,y1,z1,c1,x,y,z,c) cimg_for_in4C(img,c0,c1,c) 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_for5C(img,c) cimg_for5((img)._spectrum,c)
+#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_for5XC(img,x,c) cimg_for5C(img,c) cimg_for5X(img,x)
+#define cimg_for5YZ(img,y,z) cimg_for5Z(img,z) cimg_for5Y(img,y)
+#define cimg_for5YC(img,y,c) cimg_for5C(img,c) cimg_for5Y(img,y)
+#define cimg_for5ZC(img,z,c) cimg_for5C(img,c) cimg_for5Z(img,z)
+#define cimg_for5XYZ(img,x,y,z) cimg_for5Z(img,z) cimg_for5XY(img,x,y)
+#define cimg_for5XZC(img,x,z,c) cimg_for5C(img,c) cimg_for5XZ(img,x,z)
+#define cimg_for5YZC(img,y,z,c) cimg_for5C(img,c) cimg_for5YZ(img,y,z)
+#define cimg_for5XYZC(img,x,y,z,c) cimg_for5C(img,c) 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_in5C(img,c0,c1,c) cimg_for_in5((img)._spectrum,c0,c1,c)
+#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_in5XC(img,x0,c0,x1,c1,x,c) cimg_for_in5C(img,c0,c1,c) 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_in5YC(img,y0,c0,y1,c1,y,c) cimg_for_in5C(img,c0,c1,c) cimg_for_in5Y(img,y0,y1,y)
+#define cimg_for_in5ZC(img,z0,c0,z1,c1,z,c) cimg_for_in5C(img,c0,c1,c) 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_in5XZC(img,x0,z0,c0,x1,y1,c1,x,z,c) cimg_for_in5C(img,c0,c1,c) cimg_for_in5XZ(img,x0,y0,x1,y1,x,z)
+#define cimg_for_in5YZC(img,y0,z0,c0,y1,z1,c1,y,z,c) cimg_for_in5C(img,c0,c1,c) cimg_for_in5YZ(img,y0,z0,y1,z1,y,z)
+#define cimg_for_in5XYZC(img,x0,y0,z0,c0,x1,y1,z1,c1,x,y,z,c) cimg_for_in5C(img,c0,c1,c) 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_for6C(img,c) cimg_for6((img)._spectrum,c)
+#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_for6XC(img,x,c) cimg_for6C(img,c) cimg_for6X(img,x)
+#define cimg_for6YZ(img,y,z) cimg_for6Z(img,z) cimg_for6Y(img,y)
+#define cimg_for6YC(img,y,c) cimg_for6C(img,c) cimg_for6Y(img,y)
+#define cimg_for6ZC(img,z,c) cimg_for6C(img,c) cimg_for6Z(img,z)
+#define cimg_for6XYZ(img,x,y,z) cimg_for6Z(img,z) cimg_for6XY(img,x,y)
+#define cimg_for6XZC(img,x,z,c) cimg_for6C(img,c) cimg_for6XZ(img,x,z)
+#define cimg_for6YZC(img,y,z,c) cimg_for6C(img,c) cimg_for6YZ(img,y,z)
+#define cimg_for6XYZC(img,x,y,z,c) cimg_for6C(img,c) 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_in6C(img,c0,c1,c) cimg_for_in6((img)._spectrum,c0,c1,c)
+#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_in6XC(img,x0,c0,x1,c1,x,c) cimg_for_in6C(img,c0,c1,c) 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_in6YC(img,y0,c0,y1,c1,y,c) cimg_for_in6C(img,c0,c1,c) cimg_for_in6Y(img,y0,y1,y)
+#define cimg_for_in6ZC(img,z0,c0,z1,c1,z,c) cimg_for_in6C(img,c0,c1,c) 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_in6XZC(img,x0,z0,c0,x1,y1,c1,x,z,c) cimg_for_in6C(img,c0,c1,c) cimg_for_in6XZ(img,x0,y0,x1,y1,x,z)
+#define cimg_for_in6YZC(img,y0,z0,c0,y1,z1,c1,y,z,c) cimg_for_in6C(img,c0,c1,c) cimg_for_in6YZ(img,y0,z0,y1,z1,y,z)
+#define cimg_for_in6XYZC(img,x0,y0,z0,c0,x1,y1,z1,c1,x,y,z,c) cimg_for_in6C(img,c0,c1,c) 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_for7C(img,c) cimg_for7((img)._spectrum,c)
+#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_for7XC(img,x,c) cimg_for7C(img,c) cimg_for7X(img,x)
+#define cimg_for7YZ(img,y,z) cimg_for7Z(img,z) cimg_for7Y(img,y)
+#define cimg_for7YC(img,y,c) cimg_for7C(img,c) cimg_for7Y(img,y)
+#define cimg_for7ZC(img,z,c) cimg_for7C(img,c) cimg_for7Z(img,z)
+#define cimg_for7XYZ(img,x,y,z) cimg_for7Z(img,z) cimg_for7XY(img,x,y)
+#define cimg_for7XZC(img,x,z,c) cimg_for7C(img,c) cimg_for7XZ(img,x,z)
+#define cimg_for7YZC(img,y,z,c) cimg_for7C(img,c) cimg_for7YZ(img,y,z)
+#define cimg_for7XYZC(img,x,y,z,c) cimg_for7C(img,c) 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_in7C(img,c0,c1,c) cimg_for_in7((img)._spectrum,c0,c1,c)
+#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_in7XC(img,x0,c0,x1,c1,x,c) cimg_for_in7C(img,c0,c1,c) 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_in7YC(img,y0,c0,y1,c1,y,c) cimg_for_in7C(img,c0,c1,c) cimg_for_in7Y(img,y0,y1,y)
+#define cimg_for_in7ZC(img,z0,c0,z1,c1,z,c) cimg_for_in7C(img,c0,c1,c) 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_in7XZC(img,x0,z0,c0,x1,y1,c1,x,z,c) cimg_for_in7C(img,c0,c1,c) cimg_for_in7XZ(img,x0,y0,x1,y1,x,z)
+#define cimg_for_in7YZC(img,y0,z0,c0,y1,z1,c1,y,z,c) cimg_for_in7C(img,c0,c1,c) cimg_for_in7YZ(img,y0,z0,y1,z1,y,z)
+#define cimg_for_in7XYZC(img,x0,y0,z0,c0,x1,y1,z1,c1,x,y,z,c) cimg_for_in7C(img,c0,c1,c) 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_for8C(img,c) cimg_for8((img)._spectrum,c)
+#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_for8XC(img,x,c) cimg_for8C(img,c) cimg_for8X(img,x)
+#define cimg_for8YZ(img,y,z) cimg_for8Z(img,z) cimg_for8Y(img,y)
+#define cimg_for8YC(img,y,c) cimg_for8C(img,c) cimg_for8Y(img,y)
+#define cimg_for8ZC(img,z,c) cimg_for8C(img,c) cimg_for8Z(img,z)
+#define cimg_for8XYZ(img,x,y,z) cimg_for8Z(img,z) cimg_for8XY(img,x,y)
+#define cimg_for8XZC(img,x,z,c) cimg_for8C(img,c) cimg_for8XZ(img,x,z)
+#define cimg_for8YZC(img,y,z,c) cimg_for8C(img,c) cimg_for8YZ(img,y,z)
+#define cimg_for8XYZC(img,x,y,z,c) cimg_for8C(img,c) 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_in8C(img,c0,c1,c) cimg_for_in8((img)._spectrum,c0,c1,c)
+#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_in8XC(img,x0,c0,x1,c1,x,c) cimg_for_in8C(img,c0,c1,c) 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_in8YC(img,y0,c0,y1,c1,y,c) cimg_for_in8C(img,c0,c1,c) cimg_for_in8Y(img,y0,y1,y)
+#define cimg_for_in8ZC(img,z0,c0,z1,c1,z,c) cimg_for_in8C(img,c0,c1,c) 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_in8XZC(img,x0,z0,c0,x1,y1,c1,x,z,c) cimg_for_in8C(img,c0,c1,c) cimg_for_in8XZ(img,x0,y0,x1,y1,x,z)
+#define cimg_for_in8YZC(img,y0,z0,c0,y1,z1,c1,y,z,c) cimg_for_in8C(img,c0,c1,c) cimg_for_in8YZ(img,y0,z0,y1,z1,y,z)
+#define cimg_for_in8XYZC(img,x0,y0,z0,c0,x1,y1,z1,c1,x,y,z,c) cimg_for_in8C(img,c0,c1,c) 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_for9C(img,c) cimg_for9((img)._spectrum,c)
+#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_for9XC(img,x,c) cimg_for9C(img,c) cimg_for9X(img,x)
+#define cimg_for9YZ(img,y,z) cimg_for9Z(img,z) cimg_for9Y(img,y)
+#define cimg_for9YC(img,y,c) cimg_for9C(img,c) cimg_for9Y(img,y)
+#define cimg_for9ZC(img,z,c) cimg_for9C(img,c) cimg_for9Z(img,z)
+#define cimg_for9XYZ(img,x,y,z) cimg_for9Z(img,z) cimg_for9XY(img,x,y)
+#define cimg_for9XZC(img,x,z,c) cimg_for9C(img,c) cimg_for9XZ(img,x,z)
+#define cimg_for9YZC(img,y,z,c) cimg_for9C(img,c) cimg_for9YZ(img,y,z)
+#define cimg_for9XYZC(img,x,y,z,c) cimg_for9C(img,c) 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_in9C(img,c0,c1,c) cimg_for_in9((img)._spectrum,c0,c1,c)
+#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_in9XC(img,x0,c0,x1,c1,x,c) cimg_for_in9C(img,c0,c1,c) 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_in9YC(img,y0,c0,y1,c1,y,c) cimg_for_in9C(img,c0,c1,c) cimg_for_in9Y(img,y0,y1,y)
+#define cimg_for_in9ZC(img,z0,c0,z1,c1,z,c) cimg_for_in9C(img,c0,c1,c) 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_in9XZC(img,x0,z0,c0,x1,y1,c1,x,z,c) cimg_for_in9C(img,c0,c1,c) cimg_for_in9XZ(img,x0,y0,x1,y1,x,z)
+#define cimg_for_in9YZC(img,y0,z0,c0,y1,z1,c1,y,z,c) cimg_for_in9C(img,c0,c1,c) cimg_for_in9YZ(img,y0,z0,y1,z1,y,z)
+#define cimg_for_in9XYZC(img,x0,y0,z0,c0,x1,y1,z1,c1,x,y,z,c) cimg_for_in9C(img,c0,c1,c) cimg_for_in9XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z)
+
+#define cimg_for2x2(img,x,y,z,c,I,T) \
+  cimg_for2((img)._height,y) for (int x = 0, \
+   _n1##x = (int)( \
+   (I[0] = (T)(img)(0,y,z,c)), \
+   (I[2] = (T)(img)(0,_n1##y,z,c)), \
+   1>=(img)._width?(img).width()-1:1);  \
+   (_n1##x<(img).width() && ( \
+   (I[1] = (T)(img)(_n1##x,y,z,c)), \
+   (I[3] = (T)(img)(_n1##x,_n1##y,z,c)),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,c,I,T) \
+  cimg_for_in2((img)._height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)(x0), \
+   _n1##x = (int)( \
+   (I[0] = (T)(img)(x,y,z,c)), \
+   (I[2] = (T)(img)(x,_n1##y,z,c)), \
+   x+1>=(int)(img)._width?(img).width()-1:x+1); \
+   x<=(int)(x1) && ((_n1##x<(img).width() && (  \
+   (I[1] = (T)(img)(_n1##x,y,z,c)), \
+   (I[3] = (T)(img)(_n1##x,_n1##y,z,c)),1)) || \
+   x==--_n1##x); \
+   I[0] = I[1], \
+   I[2] = I[3], \
+   ++x, ++_n1##x)
+
+#define cimg_for3x3(img,x,y,z,c,I,T) \
+  cimg_for3((img)._height,y) for (int x = 0, \
+   _p1##x = 0, \
+   _n1##x = (int)( \
+   (I[0] = I[1] = (T)(img)(_p1##x,_p1##y,z,c)), \
+   (I[3] = I[4] = (T)(img)(0,y,z,c)), \
+   (I[6] = I[7] = (T)(img)(0,_n1##y,z,c)),      \
+   1>=(img)._width?(img).width()-1:1); \
+   (_n1##x<(img).width() && ( \
+   (I[2] = (T)(img)(_n1##x,_p1##y,z,c)), \
+   (I[5] = (T)(img)(_n1##x,y,z,c)), \
+   (I[8] = (T)(img)(_n1##x,_n1##y,z,c)),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,c,I,T) \
+  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] = (T)(img)(_p1##x,_p1##y,z,c)), \
+   (I[3] = (T)(img)(_p1##x,y,z,c)), \
+   (I[6] = (T)(img)(_p1##x,_n1##y,z,c)), \
+   (I[1] = (T)(img)(x,_p1##y,z,c)), \
+   (I[4] = (T)(img)(x,y,z,c)), \
+   (I[7] = (T)(img)(x,_n1##y,z,c)), \
+   x+1>=(int)(img)._width?(img).width()-1:x+1); \
+   x<=(int)(x1) && ((_n1##x<(img).width() && ( \
+   (I[2] = (T)(img)(_n1##x,_p1##y,z,c)), \
+   (I[5] = (T)(img)(_n1##x,y,z,c)), \
+   (I[8] = (T)(img)(_n1##x,_n1##y,z,c)),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,c,I,T) \
+  cimg_for4((img)._height,y) for (int x = 0, \
+   _p1##x = 0, \
+   _n1##x = 1>=(img)._width?(img).width()-1:1, \
+   _n2##x = (int)( \
+   (I[0] = I[1] = (T)(img)(_p1##x,_p1##y,z,c)), \
+   (I[4] = I[5] = (T)(img)(0,y,z,c)), \
+   (I[8] = I[9] = (T)(img)(0,_n1##y,z,c)), \
+   (I[12] = I[13] = (T)(img)(0,_n2##y,z,c)), \
+   (I[2] = (T)(img)(_n1##x,_p1##y,z,c)), \
+   (I[6] = (T)(img)(_n1##x,y,z,c)), \
+   (I[10] = (T)(img)(_n1##x,_n1##y,z,c)), \
+   (I[14] = (T)(img)(_n1##x,_n2##y,z,c)), \
+   2>=(img)._width?(img).width()-1:2); \
+   (_n2##x<(img).width() && ( \
+   (I[3] = (T)(img)(_n2##x,_p1##y,z,c)), \
+   (I[7] = (T)(img)(_n2##x,y,z,c)), \
+   (I[11] = (T)(img)(_n2##x,_n1##y,z,c)), \
+   (I[15] = (T)(img)(_n2##x,_n2##y,z,c)),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,c,I,T) \
+  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?(img).width()-1:x+1, \
+   _n2##x = (int)( \
+   (I[0] = (T)(img)(_p1##x,_p1##y,z,c)), \
+   (I[4] = (T)(img)(_p1##x,y,z,c)), \
+   (I[8] = (T)(img)(_p1##x,_n1##y,z,c)), \
+   (I[12] = (T)(img)(_p1##x,_n2##y,z,c)), \
+   (I[1] = (T)(img)(x,_p1##y,z,c)), \
+   (I[5] = (T)(img)(x,y,z,c)), \
+   (I[9] = (T)(img)(x,_n1##y,z,c)), \
+   (I[13] = (T)(img)(x,_n2##y,z,c)), \
+   (I[2] = (T)(img)(_n1##x,_p1##y,z,c)), \
+   (I[6] = (T)(img)(_n1##x,y,z,c)), \
+   (I[10] = (T)(img)(_n1##x,_n1##y,z,c)), \
+   (I[14] = (T)(img)(_n1##x,_n2##y,z,c)), \
+   x+2>=(int)(img)._width?(img).width()-1:x+2); \
+   x<=(int)(x1) && ((_n2##x<(img).width() && ( \
+   (I[3] = (T)(img)(_n2##x,_p1##y,z,c)), \
+   (I[7] = (T)(img)(_n2##x,y,z,c)), \
+   (I[11] = (T)(img)(_n2##x,_n1##y,z,c)), \
+   (I[15] = (T)(img)(_n2##x,_n2##y,z,c)),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,c,I,T) \
+ cimg_for5((img)._height,y) for (int x = 0, \
+   _p2##x = 0, _p1##x = 0, \
+   _n1##x = 1>=(img)._width?(img).width()-1:1, \
+   _n2##x = (int)( \
+   (I[0] = I[1] = I[2] = (T)(img)(_p2##x,_p2##y,z,c)), \
+   (I[5] = I[6] = I[7] = (T)(img)(0,_p1##y,z,c)), \
+   (I[10] = I[11] = I[12] = (T)(img)(0,y,z,c)), \
+   (I[15] = I[16] = I[17] = (T)(img)(0,_n1##y,z,c)), \
+   (I[20] = I[21] = I[22] = (T)(img)(0,_n2##y,z,c)), \
+   (I[3] = (T)(img)(_n1##x,_p2##y,z,c)), \
+   (I[8] = (T)(img)(_n1##x,_p1##y,z,c)), \
+   (I[13] = (T)(img)(_n1##x,y,z,c)), \
+   (I[18] = (T)(img)(_n1##x,_n1##y,z,c)), \
+   (I[23] = (T)(img)(_n1##x,_n2##y,z,c)),  \
+   2>=(img)._width?(img).width()-1:2); \
+   (_n2##x<(img).width() && ( \
+   (I[4] = (T)(img)(_n2##x,_p2##y,z,c)), \
+   (I[9] = (T)(img)(_n2##x,_p1##y,z,c)), \
+   (I[14] = (T)(img)(_n2##x,y,z,c)), \
+   (I[19] = (T)(img)(_n2##x,_n1##y,z,c)), \
+   (I[24] = (T)(img)(_n2##x,_n2##y,z,c)),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,c,I,T) \
+ 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?(img).width()-1:x+1, \
+   _n2##x = (int)( \
+   (I[0] = (T)(img)(_p2##x,_p2##y,z,c)), \
+   (I[5] = (T)(img)(_p2##x,_p1##y,z,c)), \
+   (I[10] = (T)(img)(_p2##x,y,z,c)), \
+   (I[15] = (T)(img)(_p2##x,_n1##y,z,c)), \
+   (I[20] = (T)(img)(_p2##x,_n2##y,z,c)), \
+   (I[1] = (T)(img)(_p1##x,_p2##y,z,c)), \
+   (I[6] = (T)(img)(_p1##x,_p1##y,z,c)), \
+   (I[11] = (T)(img)(_p1##x,y,z,c)), \
+   (I[16] = (T)(img)(_p1##x,_n1##y,z,c)), \
+   (I[21] = (T)(img)(_p1##x,_n2##y,z,c)), \
+   (I[2] = (T)(img)(x,_p2##y,z,c)), \
+   (I[7] = (T)(img)(x,_p1##y,z,c)), \
+   (I[12] = (T)(img)(x,y,z,c)), \
+   (I[17] = (T)(img)(x,_n1##y,z,c)), \
+   (I[22] = (T)(img)(x,_n2##y,z,c)), \
+   (I[3] = (T)(img)(_n1##x,_p2##y,z,c)), \
+   (I[8] = (T)(img)(_n1##x,_p1##y,z,c)), \
+   (I[13] = (T)(img)(_n1##x,y,z,c)), \
+   (I[18] = (T)(img)(_n1##x,_n1##y,z,c)), \
+   (I[23] = (T)(img)(_n1##x,_n2##y,z,c)), \
+   x+2>=(int)(img)._width?(img).width()-1:x+2); \
+   x<=(int)(x1) && ((_n2##x<(img).width() && ( \
+   (I[4] = (T)(img)(_n2##x,_p2##y,z,c)), \
+   (I[9] = (T)(img)(_n2##x,_p1##y,z,c)), \
+   (I[14] = (T)(img)(_n2##x,y,z,c)), \
+   (I[19] = (T)(img)(_n2##x,_n1##y,z,c)), \
+   (I[24] = (T)(img)(_n2##x,_n2##y,z,c)),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,c,I,T) \
+ cimg_for6((img)._height,y) for (int x = 0, \
+   _p2##x = 0, _p1##x = 0, \
+   _n1##x = 1>=(img)._width?(img).width()-1:1, \
+   _n2##x = 2>=(img)._width?(img).width()-1:2, \
+   _n3##x = (int)( \
+   (I[0] = I[1] = I[2] = (T)(img)(_p2##x,_p2##y,z,c)), \
+   (I[6] = I[7] = I[8] = (T)(img)(0,_p1##y,z,c)), \
+   (I[12] = I[13] = I[14] = (T)(img)(0,y,z,c)), \
+   (I[18] = I[19] = I[20] = (T)(img)(0,_n1##y,z,c)), \
+   (I[24] = I[25] = I[26] = (T)(img)(0,_n2##y,z,c)), \
+   (I[30] = I[31] = I[32] = (T)(img)(0,_n3##y,z,c)), \
+   (I[3] = (T)(img)(_n1##x,_p2##y,z,c)), \
+   (I[9] = (T)(img)(_n1##x,_p1##y,z,c)), \
+   (I[15] = (T)(img)(_n1##x,y,z,c)), \
+   (I[21] = (T)(img)(_n1##x,_n1##y,z,c)), \
+   (I[27] = (T)(img)(_n1##x,_n2##y,z,c)), \
+   (I[33] = (T)(img)(_n1##x,_n3##y,z,c)), \
+   (I[4] = (T)(img)(_n2##x,_p2##y,z,c)), \
+   (I[10] = (T)(img)(_n2##x,_p1##y,z,c)), \
+   (I[16] = (T)(img)(_n2##x,y,z,c)), \
+   (I[22] = (T)(img)(_n2##x,_n1##y,z,c)), \
+   (I[28] = (T)(img)(_n2##x,_n2##y,z,c)), \
+   (I[34] = (T)(img)(_n2##x,_n3##y,z,c)), \
+   3>=(img)._width?(img).width()-1:3); \
+   (_n3##x<(img).width() && ( \
+   (I[5] = (T)(img)(_n3##x,_p2##y,z,c)), \
+   (I[11] = (T)(img)(_n3##x,_p1##y,z,c)), \
+   (I[17] = (T)(img)(_n3##x,y,z,c)), \
+   (I[23] = (T)(img)(_n3##x,_n1##y,z,c)), \
+   (I[29] = (T)(img)(_n3##x,_n2##y,z,c)), \
+   (I[35] = (T)(img)(_n3##x,_n3##y,z,c)),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,c,I,T) \
+  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?(img).width()-1:x+1, \
+   _n2##x = x+2>=(int)(img)._width?(img).width()-1:x+2, \
+   _n3##x = (int)( \
+   (I[0] = (T)(img)(_p2##x,_p2##y,z,c)), \
+   (I[6] = (T)(img)(_p2##x,_p1##y,z,c)), \
+   (I[12] = (T)(img)(_p2##x,y,z,c)), \
+   (I[18] = (T)(img)(_p2##x,_n1##y,z,c)), \
+   (I[24] = (T)(img)(_p2##x,_n2##y,z,c)), \
+   (I[30] = (T)(img)(_p2##x,_n3##y,z,c)), \
+   (I[1] = (T)(img)(_p1##x,_p2##y,z,c)), \
+   (I[7] = (T)(img)(_p1##x,_p1##y,z,c)), \
+   (I[13] = (T)(img)(_p1##x,y,z,c)), \
+   (I[19] = (T)(img)(_p1##x,_n1##y,z,c)), \
+   (I[25] = (T)(img)(_p1##x,_n2##y,z,c)), \
+   (I[31] = (T)(img)(_p1##x,_n3##y,z,c)), \
+   (I[2] = (T)(img)(x,_p2##y,z,c)), \
+   (I[8] = (T)(img)(x,_p1##y,z,c)), \
+   (I[14] = (T)(img)(x,y,z,c)), \
+   (I[20] = (T)(img)(x,_n1##y,z,c)), \
+   (I[26] = (T)(img)(x,_n2##y,z,c)), \
+   (I[32] = (T)(img)(x,_n3##y,z,c)), \
+   (I[3] = (T)(img)(_n1##x,_p2##y,z,c)), \
+   (I[9] = (T)(img)(_n1##x,_p1##y,z,c)), \
+   (I[15] = (T)(img)(_n1##x,y,z,c)), \
+   (I[21] = (T)(img)(_n1##x,_n1##y,z,c)), \
+   (I[27] = (T)(img)(_n1##x,_n2##y,z,c)), \
+   (I[33] = (T)(img)(_n1##x,_n3##y,z,c)), \
+   (I[4] = (T)(img)(_n2##x,_p2##y,z,c)), \
+   (I[10] = (T)(img)(_n2##x,_p1##y,z,c)), \
+   (I[16] = (T)(img)(_n2##x,y,z,c)), \
+   (I[22] = (T)(img)(_n2##x,_n1##y,z,c)), \
+   (I[28] = (T)(img)(_n2##x,_n2##y,z,c)), \
+   (I[34] = (T)(img)(_n2##x,_n3##y,z,c)), \
+   x+3>=(int)(img)._width?(img).width()-1:x+3); \
+   x<=(int)(x1) && ((_n3##x<(img).width() && ( \
+   (I[5] = (T)(img)(_n3##x,_p2##y,z,c)), \
+   (I[11] = (T)(img)(_n3##x,_p1##y,z,c)), \
+   (I[17] = (T)(img)(_n3##x,y,z,c)), \
+   (I[23] = (T)(img)(_n3##x,_n1##y,z,c)), \
+   (I[29] = (T)(img)(_n3##x,_n2##y,z,c)), \
+   (I[35] = (T)(img)(_n3##x,_n3##y,z,c)),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,c,I,T) \
+  cimg_for7((img)._height,y) for (int x = 0, \
+   _p3##x = 0, _p2##x = 0, _p1##x = 0, \
+   _n1##x = 1>=(img)._width?(img).width()-1:1, \
+   _n2##x = 2>=(img)._width?(img).width()-1:2, \
+   _n3##x = (int)( \
+   (I[0] = I[1] = I[2] = I[3] = (T)(img)(_p3##x,_p3##y,z,c)), \
+   (I[7] = I[8] = I[9] = I[10] = (T)(img)(0,_p2##y,z,c)), \
+   (I[14] = I[15] = I[16] = I[17] = (T)(img)(0,_p1##y,z,c)), \
+   (I[21] = I[22] = I[23] = I[24] = (T)(img)(0,y,z,c)), \
+   (I[28] = I[29] = I[30] = I[31] = (T)(img)(0,_n1##y,z,c)), \
+   (I[35] = I[36] = I[37] = I[38] = (T)(img)(0,_n2##y,z,c)), \
+   (I[42] = I[43] = I[44] = I[45] = (T)(img)(0,_n3##y,z,c)), \
+   (I[4] = (T)(img)(_n1##x,_p3##y,z,c)), \
+   (I[11] = (T)(img)(_n1##x,_p2##y,z,c)), \
+   (I[18] = (T)(img)(_n1##x,_p1##y,z,c)), \
+   (I[25] = (T)(img)(_n1##x,y,z,c)), \
+   (I[32] = (T)(img)(_n1##x,_n1##y,z,c)), \
+   (I[39] = (T)(img)(_n1##x,_n2##y,z,c)), \
+   (I[46] = (T)(img)(_n1##x,_n3##y,z,c)), \
+   (I[5] = (T)(img)(_n2##x,_p3##y,z,c)), \
+   (I[12] = (T)(img)(_n2##x,_p2##y,z,c)), \
+   (I[19] = (T)(img)(_n2##x,_p1##y,z,c)), \
+   (I[26] = (T)(img)(_n2##x,y,z,c)), \
+   (I[33] = (T)(img)(_n2##x,_n1##y,z,c)), \
+   (I[40] = (T)(img)(_n2##x,_n2##y,z,c)), \
+   (I[47] = (T)(img)(_n2##x,_n3##y,z,c)), \
+   3>=(img)._width?(img).width()-1:3); \
+   (_n3##x<(img).width() && ( \
+   (I[6] = (T)(img)(_n3##x,_p3##y,z,c)), \
+   (I[13] = (T)(img)(_n3##x,_p2##y,z,c)), \
+   (I[20] = (T)(img)(_n3##x,_p1##y,z,c)), \
+   (I[27] = (T)(img)(_n3##x,y,z,c)), \
+   (I[34] = (T)(img)(_n3##x,_n1##y,z,c)), \
+   (I[41] = (T)(img)(_n3##x,_n2##y,z,c)), \
+   (I[48] = (T)(img)(_n3##x,_n3##y,z,c)),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,c,I,T) \
+  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?(img).width()-1:x+1, \
+   _n2##x = x+2>=(int)(img)._width?(img).width()-1:x+2, \
+   _n3##x = (int)( \
+   (I[0] = (T)(img)(_p3##x,_p3##y,z,c)), \
+   (I[7] = (T)(img)(_p3##x,_p2##y,z,c)), \
+   (I[14] = (T)(img)(_p3##x,_p1##y,z,c)), \
+   (I[21] = (T)(img)(_p3##x,y,z,c)), \
+   (I[28] = (T)(img)(_p3##x,_n1##y,z,c)), \
+   (I[35] = (T)(img)(_p3##x,_n2##y,z,c)), \
+   (I[42] = (T)(img)(_p3##x,_n3##y,z,c)), \
+   (I[1] = (T)(img)(_p2##x,_p3##y,z,c)), \
+   (I[8] = (T)(img)(_p2##x,_p2##y,z,c)), \
+   (I[15] = (T)(img)(_p2##x,_p1##y,z,c)), \
+   (I[22] = (T)(img)(_p2##x,y,z,c)), \
+   (I[29] = (T)(img)(_p2##x,_n1##y,z,c)), \
+   (I[36] = (T)(img)(_p2##x,_n2##y,z,c)), \
+   (I[43] = (T)(img)(_p2##x,_n3##y,z,c)), \
+   (I[2] = (T)(img)(_p1##x,_p3##y,z,c)), \
+   (I[9] = (T)(img)(_p1##x,_p2##y,z,c)), \
+   (I[16] = (T)(img)(_p1##x,_p1##y,z,c)), \
+   (I[23] = (T)(img)(_p1##x,y,z,c)), \
+   (I[30] = (T)(img)(_p1##x,_n1##y,z,c)), \
+   (I[37] = (T)(img)(_p1##x,_n2##y,z,c)), \
+   (I[44] = (T)(img)(_p1##x,_n3##y,z,c)), \
+   (I[3] = (T)(img)(x,_p3##y,z,c)), \
+   (I[10] = (T)(img)(x,_p2##y,z,c)), \
+   (I[17] = (T)(img)(x,_p1##y,z,c)), \
+   (I[24] = (T)(img)(x,y,z,c)), \
+   (I[31] = (T)(img)(x,_n1##y,z,c)), \
+   (I[38] = (T)(img)(x,_n2##y,z,c)), \
+   (I[45] = (T)(img)(x,_n3##y,z,c)), \
+   (I[4] = (T)(img)(_n1##x,_p3##y,z,c)), \
+   (I[11] = (T)(img)(_n1##x,_p2##y,z,c)), \
+   (I[18] = (T)(img)(_n1##x,_p1##y,z,c)), \
+   (I[25] = (T)(img)(_n1##x,y,z,c)), \
+   (I[32] = (T)(img)(_n1##x,_n1##y,z,c)), \
+   (I[39] = (T)(img)(_n1##x,_n2##y,z,c)), \
+   (I[46] = (T)(img)(_n1##x,_n3##y,z,c)), \
+   (I[5] = (T)(img)(_n2##x,_p3##y,z,c)), \
+   (I[12] = (T)(img)(_n2##x,_p2##y,z,c)), \
+   (I[19] = (T)(img)(_n2##x,_p1##y,z,c)), \
+   (I[26] = (T)(img)(_n2##x,y,z,c)), \
+   (I[33] = (T)(img)(_n2##x,_n1##y,z,c)), \
+   (I[40] = (T)(img)(_n2##x,_n2##y,z,c)), \
+   (I[47] = (T)(img)(_n2##x,_n3##y,z,c)), \
+   x+3>=(int)(img)._width?(img).width()-1:x+3); \
+   x<=(int)(x1) && ((_n3##x<(img).width() && ( \
+   (I[6] = (T)(img)(_n3##x,_p3##y,z,c)), \
+   (I[13] = (T)(img)(_n3##x,_p2##y,z,c)), \
+   (I[20] = (T)(img)(_n3##x,_p1##y,z,c)), \
+   (I[27] = (T)(img)(_n3##x,y,z,c)), \
+   (I[34] = (T)(img)(_n3##x,_n1##y,z,c)), \
+   (I[41] = (T)(img)(_n3##x,_n2##y,z,c)), \
+   (I[48] = (T)(img)(_n3##x,_n3##y,z,c)),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,c,I,T) \
+  cimg_for8((img)._height,y) for (int x = 0, \
+   _p3##x = 0, _p2##x = 0, _p1##x = 0, \
+   _n1##x = 1>=((img)._width)?(img).width()-1:1, \
+   _n2##x = 2>=((img)._width)?(img).width()-1:2, \
+   _n3##x = 3>=((img)._width)?(img).width()-1:3, \
+   _n4##x = (int)( \
+   (I[0] = I[1] = I[2] = I[3] = (T)(img)(_p3##x,_p3##y,z,c)), \
+   (I[8] = I[9] = I[10] = I[11] = (T)(img)(0,_p2##y,z,c)), \
+   (I[16] = I[17] = I[18] = I[19] = (T)(img)(0,_p1##y,z,c)), \
+   (I[24] = I[25] = I[26] = I[27] = (T)(img)(0,y,z,c)), \
+   (I[32] = I[33] = I[34] = I[35] = (T)(img)(0,_n1##y,z,c)), \
+   (I[40] = I[41] = I[42] = I[43] = (T)(img)(0,_n2##y,z,c)), \
+   (I[48] = I[49] = I[50] = I[51] = (T)(img)(0,_n3##y,z,c)), \
+   (I[56] = I[57] = I[58] = I[59] = (T)(img)(0,_n4##y,z,c)), \
+   (I[4] = (T)(img)(_n1##x,_p3##y,z,c)), \
+   (I[12] = (T)(img)(_n1##x,_p2##y,z,c)), \
+   (I[20] = (T)(img)(_n1##x,_p1##y,z,c)), \
+   (I[28] = (T)(img)(_n1##x,y,z,c)), \
+   (I[36] = (T)(img)(_n1##x,_n1##y,z,c)), \
+   (I[44] = (T)(img)(_n1##x,_n2##y,z,c)), \
+   (I[52] = (T)(img)(_n1##x,_n3##y,z,c)), \
+   (I[60] = (T)(img)(_n1##x,_n4##y,z,c)), \
+   (I[5] = (T)(img)(_n2##x,_p3##y,z,c)), \
+   (I[13] = (T)(img)(_n2##x,_p2##y,z,c)), \
+   (I[21] = (T)(img)(_n2##x,_p1##y,z,c)), \
+   (I[29] = (T)(img)(_n2##x,y,z,c)), \
+   (I[37] = (T)(img)(_n2##x,_n1##y,z,c)), \
+   (I[45] = (T)(img)(_n2##x,_n2##y,z,c)), \
+   (I[53] = (T)(img)(_n2##x,_n3##y,z,c)), \
+   (I[61] = (T)(img)(_n2##x,_n4##y,z,c)), \
+   (I[6] = (T)(img)(_n3##x,_p3##y,z,c)), \
+   (I[14] = (T)(img)(_n3##x,_p2##y,z,c)), \
+   (I[22] = (T)(img)(_n3##x,_p1##y,z,c)), \
+   (I[30] = (T)(img)(_n3##x,y,z,c)), \
+   (I[38] = (T)(img)(_n3##x,_n1##y,z,c)), \
+   (I[46] = (T)(img)(_n3##x,_n2##y,z,c)), \
+   (I[54] = (T)(img)(_n3##x,_n3##y,z,c)), \
+   (I[62] = (T)(img)(_n3##x,_n4##y,z,c)), \
+   4>=((img)._width)?(img).width()-1:4); \
+   (_n4##x<(img).width() && ( \
+   (I[7] = (T)(img)(_n4##x,_p3##y,z,c)), \
+   (I[15] = (T)(img)(_n4##x,_p2##y,z,c)), \
+   (I[23] = (T)(img)(_n4##x,_p1##y,z,c)), \
+   (I[31] = (T)(img)(_n4##x,y,z,c)), \
+   (I[39] = (T)(img)(_n4##x,_n1##y,z,c)), \
+   (I[47] = (T)(img)(_n4##x,_n2##y,z,c)), \
+   (I[55] = (T)(img)(_n4##x,_n3##y,z,c)), \
+   (I[63] = (T)(img)(_n4##x,_n4##y,z,c)),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,c,I,T) \
+  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>=(img).width()?(img).width()-1:x+1, \
+   _n2##x = x+2>=(img).width()?(img).width()-1:x+2, \
+   _n3##x = x+3>=(img).width()?(img).width()-1:x+3, \
+   _n4##x = (int)( \
+   (I[0] = (T)(img)(_p3##x,_p3##y,z,c)), \
+   (I[8] = (T)(img)(_p3##x,_p2##y,z,c)), \
+   (I[16] = (T)(img)(_p3##x,_p1##y,z,c)), \
+   (I[24] = (T)(img)(_p3##x,y,z,c)), \
+   (I[32] = (T)(img)(_p3##x,_n1##y,z,c)), \
+   (I[40] = (T)(img)(_p3##x,_n2##y,z,c)), \
+   (I[48] = (T)(img)(_p3##x,_n3##y,z,c)), \
+   (I[56] = (T)(img)(_p3##x,_n4##y,z,c)), \
+   (I[1] = (T)(img)(_p2##x,_p3##y,z,c)), \
+   (I[9] = (T)(img)(_p2##x,_p2##y,z,c)), \
+   (I[17] = (T)(img)(_p2##x,_p1##y,z,c)), \
+   (I[25] = (T)(img)(_p2##x,y,z,c)), \
+   (I[33] = (T)(img)(_p2##x,_n1##y,z,c)), \
+   (I[41] = (T)(img)(_p2##x,_n2##y,z,c)), \
+   (I[49] = (T)(img)(_p2##x,_n3##y,z,c)), \
+   (I[57] = (T)(img)(_p2##x,_n4##y,z,c)), \
+   (I[2] = (T)(img)(_p1##x,_p3##y,z,c)), \
+   (I[10] = (T)(img)(_p1##x,_p2##y,z,c)), \
+   (I[18] = (T)(img)(_p1##x,_p1##y,z,c)), \
+   (I[26] = (T)(img)(_p1##x,y,z,c)), \
+   (I[34] = (T)(img)(_p1##x,_n1##y,z,c)), \
+   (I[42] = (T)(img)(_p1##x,_n2##y,z,c)), \
+   (I[50] = (T)(img)(_p1##x,_n3##y,z,c)), \
+   (I[58] = (T)(img)(_p1##x,_n4##y,z,c)), \
+   (I[3] = (T)(img)(x,_p3##y,z,c)), \
+   (I[11] = (T)(img)(x,_p2##y,z,c)), \
+   (I[19] = (T)(img)(x,_p1##y,z,c)), \
+   (I[27] = (T)(img)(x,y,z,c)), \
+   (I[35] = (T)(img)(x,_n1##y,z,c)), \
+   (I[43] = (T)(img)(x,_n2##y,z,c)), \
+   (I[51] = (T)(img)(x,_n3##y,z,c)), \
+   (I[59] = (T)(img)(x,_n4##y,z,c)), \
+   (I[4] = (T)(img)(_n1##x,_p3##y,z,c)), \
+   (I[12] = (T)(img)(_n1##x,_p2##y,z,c)), \
+   (I[20] = (T)(img)(_n1##x,_p1##y,z,c)), \
+   (I[28] = (T)(img)(_n1##x,y,z,c)), \
+   (I[36] = (T)(img)(_n1##x,_n1##y,z,c)), \
+   (I[44] = (T)(img)(_n1##x,_n2##y,z,c)), \
+   (I[52] = (T)(img)(_n1##x,_n3##y,z,c)), \
+   (I[60] = (T)(img)(_n1##x,_n4##y,z,c)), \
+   (I[5] = (T)(img)(_n2##x,_p3##y,z,c)), \
+   (I[13] = (T)(img)(_n2##x,_p2##y,z,c)), \
+   (I[21] = (T)(img)(_n2##x,_p1##y,z,c)), \
+   (I[29] = (T)(img)(_n2##x,y,z,c)), \
+   (I[37] = (T)(img)(_n2##x,_n1##y,z,c)), \
+   (I[45] = (T)(img)(_n2##x,_n2##y,z,c)), \
+   (I[53] = (T)(img)(_n2##x,_n3##y,z,c)), \
+   (I[61] = (T)(img)(_n2##x,_n4##y,z,c)), \
+   (I[6] = (T)(img)(_n3##x,_p3##y,z,c)), \
+   (I[14] = (T)(img)(_n3##x,_p2##y,z,c)), \
+   (I[22] = (T)(img)(_n3##x,_p1##y,z,c)), \
+   (I[30] = (T)(img)(_n3##x,y,z,c)), \
+   (I[38] = (T)(img)(_n3##x,_n1##y,z,c)), \
+   (I[46] = (T)(img)(_n3##x,_n2##y,z,c)), \
+   (I[54] = (T)(img)(_n3##x,_n3##y,z,c)), \
+   (I[62] = (T)(img)(_n3##x,_n4##y,z,c)), \
+   x+4>=(img).width()?(img).width()-1:x+4); \
+   x<=(int)(x1) && ((_n4##x<(img).width() && ( \
+   (I[7] = (T)(img)(_n4##x,_p3##y,z,c)), \
+   (I[15] = (T)(img)(_n4##x,_p2##y,z,c)), \
+   (I[23] = (T)(img)(_n4##x,_p1##y,z,c)), \
+   (I[31] = (T)(img)(_n4##x,y,z,c)), \
+   (I[39] = (T)(img)(_n4##x,_n1##y,z,c)), \
+   (I[47] = (T)(img)(_n4##x,_n2##y,z,c)), \
+   (I[55] = (T)(img)(_n4##x,_n3##y,z,c)), \
+   (I[63] = (T)(img)(_n4##x,_n4##y,z,c)),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,c,I,T) \
+  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)?(img).width()-1:1, \
+   _n2##x = 2>=((img)._width)?(img).width()-1:2, \
+   _n3##x = 3>=((img)._width)?(img).width()-1:3, \
+   _n4##x = (int)( \
+   (I[0] = I[1] = I[2] = I[3] = I[4] = (T)(img)(_p4##x,_p4##y,z,c)), \
+   (I[9] = I[10] = I[11] = I[12] = I[13] = (T)(img)(0,_p3##y,z,c)), \
+   (I[18] = I[19] = I[20] = I[21] = I[22] = (T)(img)(0,_p2##y,z,c)), \
+   (I[27] = I[28] = I[29] = I[30] = I[31] = (T)(img)(0,_p1##y,z,c)), \
+   (I[36] = I[37] = I[38] = I[39] = I[40] = (T)(img)(0,y,z,c)), \
+   (I[45] = I[46] = I[47] = I[48] = I[49] = (T)(img)(0,_n1##y,z,c)), \
+   (I[54] = I[55] = I[56] = I[57] = I[58] = (T)(img)(0,_n2##y,z,c)), \
+   (I[63] = I[64] = I[65] = I[66] = I[67] = (T)(img)(0,_n3##y,z,c)), \
+   (I[72] = I[73] = I[74] = I[75] = I[76] = (T)(img)(0,_n4##y,z,c)), \
+   (I[5] = (T)(img)(_n1##x,_p4##y,z,c)), \
+   (I[14] = (T)(img)(_n1##x,_p3##y,z,c)), \
+   (I[23] = (T)(img)(_n1##x,_p2##y,z,c)), \
+   (I[32] = (T)(img)(_n1##x,_p1##y,z,c)), \
+   (I[41] = (T)(img)(_n1##x,y,z,c)), \
+   (I[50] = (T)(img)(_n1##x,_n1##y,z,c)), \
+   (I[59] = (T)(img)(_n1##x,_n2##y,z,c)), \
+   (I[68] = (T)(img)(_n1##x,_n3##y,z,c)), \
+   (I[77] = (T)(img)(_n1##x,_n4##y,z,c)), \
+   (I[6] = (T)(img)(_n2##x,_p4##y,z,c)), \
+   (I[15] = (T)(img)(_n2##x,_p3##y,z,c)), \
+   (I[24] = (T)(img)(_n2##x,_p2##y,z,c)), \
+   (I[33] = (T)(img)(_n2##x,_p1##y,z,c)), \
+   (I[42] = (T)(img)(_n2##x,y,z,c)), \
+   (I[51] = (T)(img)(_n2##x,_n1##y,z,c)), \
+   (I[60] = (T)(img)(_n2##x,_n2##y,z,c)), \
+   (I[69] = (T)(img)(_n2##x,_n3##y,z,c)), \
+   (I[78] = (T)(img)(_n2##x,_n4##y,z,c)), \
+   (I[7] = (T)(img)(_n3##x,_p4##y,z,c)), \
+   (I[16] = (T)(img)(_n3##x,_p3##y,z,c)), \
+   (I[25] = (T)(img)(_n3##x,_p2##y,z,c)), \
+   (I[34] = (T)(img)(_n3##x,_p1##y,z,c)), \
+   (I[43] = (T)(img)(_n3##x,y,z,c)), \
+   (I[52] = (T)(img)(_n3##x,_n1##y,z,c)), \
+   (I[61] = (T)(img)(_n3##x,_n2##y,z,c)), \
+   (I[70] = (T)(img)(_n3##x,_n3##y,z,c)), \
+   (I[79] = (T)(img)(_n3##x,_n4##y,z,c)), \
+   4>=((img)._width)?(img).width()-1:4); \
+   (_n4##x<(img).width() && ( \
+   (I[8] = (T)(img)(_n4##x,_p4##y,z,c)), \
+   (I[17] = (T)(img)(_n4##x,_p3##y,z,c)), \
+   (I[26] = (T)(img)(_n4##x,_p2##y,z,c)), \
+   (I[35] = (T)(img)(_n4##x,_p1##y,z,c)), \
+   (I[44] = (T)(img)(_n4##x,y,z,c)), \
+   (I[53] = (T)(img)(_n4##x,_n1##y,z,c)), \
+   (I[62] = (T)(img)(_n4##x,_n2##y,z,c)), \
+   (I[71] = (T)(img)(_n4##x,_n3##y,z,c)), \
+   (I[80] = (T)(img)(_n4##x,_n4##y,z,c)),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,c,I,T) \
+  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>=(img).width()?(img).width()-1:x+1, \
+   _n2##x = x+2>=(img).width()?(img).width()-1:x+2, \
+   _n3##x = x+3>=(img).width()?(img).width()-1:x+3, \
+   _n4##x = (int)( \
+   (I[0] = (T)(img)(_p4##x,_p4##y,z,c)), \
+   (I[9] = (T)(img)(_p4##x,_p3##y,z,c)), \
+   (I[18] = (T)(img)(_p4##x,_p2##y,z,c)), \
+   (I[27] = (T)(img)(_p4##x,_p1##y,z,c)), \
+   (I[36] = (T)(img)(_p4##x,y,z,c)), \
+   (I[45] = (T)(img)(_p4##x,_n1##y,z,c)), \
+   (I[54] = (T)(img)(_p4##x,_n2##y,z,c)), \
+   (I[63] = (T)(img)(_p4##x,_n3##y,z,c)), \
+   (I[72] = (T)(img)(_p4##x,_n4##y,z,c)), \
+   (I[1] = (T)(img)(_p3##x,_p4##y,z,c)), \
+   (I[10] = (T)(img)(_p3##x,_p3##y,z,c)), \
+   (I[19] = (T)(img)(_p3##x,_p2##y,z,c)), \
+   (I[28] = (T)(img)(_p3##x,_p1##y,z,c)), \
+   (I[37] = (T)(img)(_p3##x,y,z,c)), \
+   (I[46] = (T)(img)(_p3##x,_n1##y,z,c)), \
+   (I[55] = (T)(img)(_p3##x,_n2##y,z,c)), \
+   (I[64] = (T)(img)(_p3##x,_n3##y,z,c)), \
+   (I[73] = (T)(img)(_p3##x,_n4##y,z,c)), \
+   (I[2] = (T)(img)(_p2##x,_p4##y,z,c)), \
+   (I[11] = (T)(img)(_p2##x,_p3##y,z,c)), \
+   (I[20] = (T)(img)(_p2##x,_p2##y,z,c)), \
+   (I[29] = (T)(img)(_p2##x,_p1##y,z,c)), \
+   (I[38] = (T)(img)(_p2##x,y,z,c)), \
+   (I[47] = (T)(img)(_p2##x,_n1##y,z,c)), \
+   (I[56] = (T)(img)(_p2##x,_n2##y,z,c)), \
+   (I[65] = (T)(img)(_p2##x,_n3##y,z,c)), \
+   (I[74] = (T)(img)(_p2##x,_n4##y,z,c)), \
+   (I[3] = (T)(img)(_p1##x,_p4##y,z,c)), \
+   (I[12] = (T)(img)(_p1##x,_p3##y,z,c)), \
+   (I[21] = (T)(img)(_p1##x,_p2##y,z,c)), \
+   (I[30] = (T)(img)(_p1##x,_p1##y,z,c)), \
+   (I[39] = (T)(img)(_p1##x,y,z,c)), \
+   (I[48] = (T)(img)(_p1##x,_n1##y,z,c)), \
+   (I[57] = (T)(img)(_p1##x,_n2##y,z,c)), \
+   (I[66] = (T)(img)(_p1##x,_n3##y,z,c)), \
+   (I[75] = (T)(img)(_p1##x,_n4##y,z,c)), \
+   (I[4] = (T)(img)(x,_p4##y,z,c)), \
+   (I[13] = (T)(img)(x,_p3##y,z,c)), \
+   (I[22] = (T)(img)(x,_p2##y,z,c)), \
+   (I[31] = (T)(img)(x,_p1##y,z,c)), \
+   (I[40] = (T)(img)(x,y,z,c)), \
+   (I[49] = (T)(img)(x,_n1##y,z,c)), \
+   (I[58] = (T)(img)(x,_n2##y,z,c)), \
+   (I[67] = (T)(img)(x,_n3##y,z,c)), \
+   (I[76] = (T)(img)(x,_n4##y,z,c)), \
+   (I[5] = (T)(img)(_n1##x,_p4##y,z,c)), \
+   (I[14] = (T)(img)(_n1##x,_p3##y,z,c)), \
+   (I[23] = (T)(img)(_n1##x,_p2##y,z,c)), \
+   (I[32] = (T)(img)(_n1##x,_p1##y,z,c)), \
+   (I[41] = (T)(img)(_n1##x,y,z,c)), \
+   (I[50] = (T)(img)(_n1##x,_n1##y,z,c)), \
+   (I[59] = (T)(img)(_n1##x,_n2##y,z,c)), \
+   (I[68] = (T)(img)(_n1##x,_n3##y,z,c)), \
+   (I[77] = (T)(img)(_n1##x,_n4##y,z,c)), \
+   (I[6] = (T)(img)(_n2##x,_p4##y,z,c)), \
+   (I[15] = (T)(img)(_n2##x,_p3##y,z,c)), \
+   (I[24] = (T)(img)(_n2##x,_p2##y,z,c)), \
+   (I[33] = (T)(img)(_n2##x,_p1##y,z,c)), \
+   (I[42] = (T)(img)(_n2##x,y,z,c)), \
+   (I[51] = (T)(img)(_n2##x,_n1##y,z,c)), \
+   (I[60] = (T)(img)(_n2##x,_n2##y,z,c)), \
+   (I[69] = (T)(img)(_n2##x,_n3##y,z,c)), \
+   (I[78] = (T)(img)(_n2##x,_n4##y,z,c)), \
+   (I[7] = (T)(img)(_n3##x,_p4##y,z,c)), \
+   (I[16] = (T)(img)(_n3##x,_p3##y,z,c)), \
+   (I[25] = (T)(img)(_n3##x,_p2##y,z,c)), \
+   (I[34] = (T)(img)(_n3##x,_p1##y,z,c)), \
+   (I[43] = (T)(img)(_n3##x,y,z,c)), \
+   (I[52] = (T)(img)(_n3##x,_n1##y,z,c)), \
+   (I[61] = (T)(img)(_n3##x,_n2##y,z,c)), \
+   (I[70] = (T)(img)(_n3##x,_n3##y,z,c)), \
+   (I[79] = (T)(img)(_n3##x,_n4##y,z,c)), \
+   x+4>=(img).width()?(img).width()-1:x+4); \
+   x<=(int)(x1) && ((_n4##x<(img).width() && ( \
+   (I[8] = (T)(img)(_n4##x,_p4##y,z,c)), \
+   (I[17] = (T)(img)(_n4##x,_p3##y,z,c)), \
+   (I[26] = (T)(img)(_n4##x,_p2##y,z,c)), \
+   (I[35] = (T)(img)(_n4##x,_p1##y,z,c)), \
+   (I[44] = (T)(img)(_n4##x,y,z,c)), \
+   (I[53] = (T)(img)(_n4##x,_n1##y,z,c)), \
+   (I[62] = (T)(img)(_n4##x,_n2##y,z,c)), \
+   (I[71] = (T)(img)(_n4##x,_n3##y,z,c)), \
+   (I[80] = (T)(img)(_n4##x,_n4##y,z,c)),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,c,I,T) \
+ cimg_for2((img)._depth,z) cimg_for2((img)._height,y) for (int x = 0, \
+   _n1##x = (int)( \
+   (I[0] = (T)(img)(0,y,z,c)), \
+   (I[2] = (T)(img)(0,_n1##y,z,c)), \
+   (I[4] = (T)(img)(0,y,_n1##z,c)), \
+   (I[6] = (T)(img)(0,_n1##y,_n1##z,c)), \
+   1>=(img)._width?(img).width()-1:1); \
+   (_n1##x<(img).width() && ( \
+   (I[1] = (T)(img)(_n1##x,y,z,c)), \
+   (I[3] = (T)(img)(_n1##x,_n1##y,z,c)), \
+   (I[5] = (T)(img)(_n1##x,y,_n1##z,c)), \
+   (I[7] = (T)(img)(_n1##x,_n1##y,_n1##z,c)),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,c,I,T) \
+ 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] = (T)(img)(x,y,z,c)), \
+   (I[2] = (T)(img)(x,_n1##y,z,c)), \
+   (I[4] = (T)(img)(x,y,_n1##z,c)), \
+   (I[6] = (T)(img)(x,_n1##y,_n1##z,c)), \
+   x+1>=(int)(img)._width?(img).width()-1:x+1); \
+   x<=(int)(x1) && ((_n1##x<(img).width() && ( \
+   (I[1] = (T)(img)(_n1##x,y,z,c)), \
+   (I[3] = (T)(img)(_n1##x,_n1##y,z,c)), \
+   (I[5] = (T)(img)(_n1##x,y,_n1##z,c)), \
+   (I[7] = (T)(img)(_n1##x,_n1##y,_n1##z,c)),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,c,I,T) \
+ cimg_for3((img)._depth,z) cimg_for3((img)._height,y) for (int x = 0, \
+   _p1##x = 0, \
+   _n1##x = (int)( \
+   (I[0] = I[1] = (T)(img)(_p1##x,_p1##y,_p1##z,c)), \
+   (I[3] = I[4] = (T)(img)(0,y,_p1##z,c)),  \
+   (I[6] = I[7] = (T)(img)(0,_n1##y,_p1##z,c)), \
+   (I[9] = I[10] = (T)(img)(0,_p1##y,z,c)), \
+   (I[12] = I[13] = (T)(img)(0,y,z,c)), \
+   (I[15] = I[16] = (T)(img)(0,_n1##y,z,c)), \
+   (I[18] = I[19] = (T)(img)(0,_p1##y,_n1##z,c)), \
+   (I[21] = I[22] = (T)(img)(0,y,_n1##z,c)), \
+   (I[24] = I[25] = (T)(img)(0,_n1##y,_n1##z,c)), \
+   1>=(img)._width?(img).width()-1:1); \
+   (_n1##x<(img).width() && ( \
+   (I[2] = (T)(img)(_n1##x,_p1##y,_p1##z,c)), \
+   (I[5] = (T)(img)(_n1##x,y,_p1##z,c)), \
+   (I[8] = (T)(img)(_n1##x,_n1##y,_p1##z,c)), \
+   (I[11] = (T)(img)(_n1##x,_p1##y,z,c)), \
+   (I[14] = (T)(img)(_n1##x,y,z,c)), \
+   (I[17] = (T)(img)(_n1##x,_n1##y,z,c)), \
+   (I[20] = (T)(img)(_n1##x,_p1##y,_n1##z,c)), \
+   (I[23] = (T)(img)(_n1##x,y,_n1##z,c)), \
+   (I[26] = (T)(img)(_n1##x,_n1##y,_n1##z,c)),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,c,I,T) \
+ 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] = (T)(img)(_p1##x,_p1##y,_p1##z,c)), \
+   (I[3] = (T)(img)(_p1##x,y,_p1##z,c)),  \
+   (I[6] = (T)(img)(_p1##x,_n1##y,_p1##z,c)), \
+   (I[9] = (T)(img)(_p1##x,_p1##y,z,c)), \
+   (I[12] = (T)(img)(_p1##x,y,z,c)), \
+   (I[15] = (T)(img)(_p1##x,_n1##y,z,c)), \
+   (I[18] = (T)(img)(_p1##x,_p1##y,_n1##z,c)), \
+   (I[21] = (T)(img)(_p1##x,y,_n1##z,c)), \
+   (I[24] = (T)(img)(_p1##x,_n1##y,_n1##z,c)), \
+   (I[1] = (T)(img)(x,_p1##y,_p1##z,c)), \
+   (I[4] = (T)(img)(x,y,_p1##z,c)),  \
+   (I[7] = (T)(img)(x,_n1##y,_p1##z,c)), \
+   (I[10] = (T)(img)(x,_p1##y,z,c)), \
+   (I[13] = (T)(img)(x,y,z,c)), \
+   (I[16] = (T)(img)(x,_n1##y,z,c)), \
+   (I[19] = (T)(img)(x,_p1##y,_n1##z,c)), \
+   (I[22] = (T)(img)(x,y,_n1##z,c)), \
+   (I[25] = (T)(img)(x,_n1##y,_n1##z,c)), \
+   x+1>=(int)(img)._width?(img).width()-1:x+1); \
+   x<=(int)(x1) && ((_n1##x<(img).width() && ( \
+   (I[2] = (T)(img)(_n1##x,_p1##y,_p1##z,c)), \
+   (I[5] = (T)(img)(_n1##x,y,_p1##z,c)), \
+   (I[8] = (T)(img)(_n1##x,_n1##y,_p1##z,c)), \
+   (I[11] = (T)(img)(_n1##x,_p1##y,z,c)), \
+   (I[14] = (T)(img)(_n1##x,y,z,c)), \
+   (I[17] = (T)(img)(_n1##x,_n1##y,z,c)), \
+   (I[20] = (T)(img)(_n1##x,_p1##y,_n1##z,c)), \
+   (I[23] = (T)(img)(_n1##x,y,_n1##z,c)), \
+   (I[26] = (T)(img)(_n1##x,_n1##y,_n1##z,c)),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 cimglist_for(list,l) for (int l = 0; l<(int)(list)._width; ++l)
+#define cimglist_for_in(list,l0,l1,l) \
+  for (int l = (int)(l0)<0?0:(int)(l0), _max##l = (unsigned int)l1<(list)._width?(int)(l1):(int)(list)._width-1; l<=_max##l; ++l)
+
+#define cimglist_apply(list,fn) cimglist_for(list,__##fn) (list)[__##fn].fn
+
+// Macros used to display error messages when exceptions are thrown. You should not use them.
+#define _cimgdisplay_instance "[instance(%u,%u,%u,%c%s%c)] CImgDisplay::"
+#define cimgdisplay_instance _width,_height,_normalization,_title?'\"':'[',_title?_title:"untitled",_title?'\"':']'
+#define _cimg_instance "[instance(%u,%u,%u,%u,%p,%sshared)] CImg<%s>::"
+#define cimg_instance _width,_height,_depth,_spectrum,_data,_is_shared?"":"non-",pixel_type()
+#define _cimglist_instance "[instance(%u,%u,%p)] CImgList<%s>::"
+#define cimglist_instance _width,_allocated_width,_data,pixel_type()
+
+/*------------------------------------------------
+ #
+ #
+ #  Define cimg_library:: namespace
+ #
+ #
+ -------------------------------------------------*/
+//! Contains <i>all classes and functions</i> 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_suffixed {
+
+  // Declare the four classes of the CImg Library.
+  template<typename T=float> struct CImg;
+  template<typename T=float> struct CImgList;
+  struct CImgDisplay;
+  struct CImgException;
+
+  // Declare cimg:: namespace.
+  // This is not the complete namespace definition. It only contains some
+  // necessary stuffs to ensure a correct declaration order of the classes and functions
+  // defined afterwards.
+  namespace cimg {
+
+#ifdef cimg_use_vt100
+    const char t_normal[] = { 0x1b, '[', '0', ';', '0', ';', '0', 'm', 0 };
+    const char t_black[] = { 0x1b, '[', '0', ';', '3', '0', ';', '5', '9', 'm', 0 };
+    const char t_red[] = { 0x1b, '[', '0', ';', '3', '1', ';', '5', '9', 'm', 0 };
+    const char t_green[] = { 0x1b, '[', '0', ';', '3', '2', ';', '5', '9', 'm', 0 };
+    const char t_yellow[] = { 0x1b, '[', '0', ';', '3', '3', ';', '5', '9', 'm', 0 };
+    const char t_blue[] = { 0x1b, '[', '0', ';', '3', '4', ';', '5', '9', 'm', 0 };
+    const char t_magenta[] = { 0x1b, '[', '0', ';', '3', '5', ';', '5', '9', 'm', 0 };
+    const char t_cyan[] = { 0x1b, '[', '0', ';', '3', '6', ';', '5', '9', 'm', 0 };
+    const char t_white[] = { 0x1b, '[', '0', ';', '3', '7', ';', '5', '9', 'm', 0 };
+    const char t_bold[] = { 0x1b, '[', '1', 'm', 0 };
+    const char t_underscore[] = { 0x1b, '[', '4', 'm', 0 };
+#else
+    const char t_normal[] = { 0 };
+    const char *const t_black = cimg::t_normal,
+      *const t_red = cimg::t_normal,
+      *const t_green = cimg::t_normal,
+      *const t_yellow = cimg::t_normal,
+      *const t_blue = cimg::t_normal,
+      *const t_magenta = cimg::t_normal,
+      *const t_cyan = cimg::t_normal,
+      *const t_white = cimg::t_normal,
+      *const t_bold = cimg::t_normal,
+      *const t_underscore = cimg::t_normal;
+#endif
+
+    inline std::FILE* output(std::FILE *file=0);
+    inline void info();
+
+    //! Avoid warning messages due to unused parameters. Actually do nothing.
+    template<typename T>
+    inline void unused(const T&, ...) {}
+
+    inline unsigned int& _exception_mode(const unsigned int value, const bool is_set) {
+      static unsigned int mode = cimg_verbosity;
+      if (is_set) mode = value;
+      return mode;
+    }
+
+    //! Set current \CImg exception mode.
+    /**
+       The way error messages are handled by \CImg can be changed dynamically, using this function.
+       \param mode Desired exception mode. Possible values are :
+       - \c 0 : Hide library messages (quiet mode).
+       - \c 1 : Print library messages on the console.
+       - \c 2 : Display library messages on a dialog window (default behavior).
+       - \c 3 : Do as \c 1 + add extra debug warnings (slow down the code !).
+       - \c 4 : Do as \c 2 + add extra debug warnings (slow down the code !).
+       \see exception_mode(), \c cimg_verbosity.
+     **/
+    inline unsigned int& exception_mode(const unsigned int mode) {
+      return _exception_mode(mode,true);
+    }
+
+    //! Return current \CImg exception mode.
+    /**
+       \note By default, return the value of configuration macro \c cimg_verbosity
+       \see exception_mode(unsigned int), \c cimg_verbosity.
+    **/
+    inline unsigned int& exception_mode() {
+      return _exception_mode(0,false);
+    }
+
+    inline int dialog(const char *const title, const char *const msg, const char *const button1_label="OK",
+                      const char *const button2_label=0, const char *const button3_label=0,
+                      const char *const button4_label=0, const char *const button5_label=0,
+                      const char *const button6_label=0, const bool centering=false);
+
+    inline double eval(const char *const expression, const double x=0, const double y=0, const double z=0, const double c=0);
+  }
+
+  /*---------------------------------------
+    #
+    # Define the CImgException structures
+    #
+    --------------------------------------*/
+  //! Instances of \c CImgException are thrown when errors are encountered in a \CImg function call.
+  /**
+     \par Overview
+
+      CImgException is the base class of all exceptions thrown by \CImg.
+      CImgException is never thrown itself. Derived classes that specify the type of errord are thrown instead.
+      These derived classes can be :
+
+      - \b CImgArgumentException : Thrown when one argument of a called \CImg function is invalid.
+      This is probably one of the most thrown exception by \CImg.
+      For instance, the following example throws a \c CImgArgumentException :
+      \code
+      CImg<float> img(100,100,1,3); // Define a 100x100 color image with float-valued pixels.
+      img.mirror('e');              // Try to mirror image along the (non-existing) 'e'-axis.
+      \endcode
+
+      - \b CImgDisplayException : Thrown when something went wrong during the display of images in CImgDisplay instances.
+
+      - \b CImgInstanceException : Thrown when an instance associated to a called \CImg method does not fit
+      the function requirements. For instance, the following example throws a \c CImgInstanceException :
+      \code
+      const CImg<float> img;           // Define an empty image.
+      const float value = img.at(0);   // Try to read first pixel value (does not exist).
+      \endcode
+
+      - \b CImgIOException : Thrown when an error occured when trying to load or save image files.
+      This happens when trying to read files that do not exist or with invalid formats.
+      For instance, the following example throws a \c CImgIOException :
+      \code
+      const CImg<float> img("missing_file.jpg");  // Try to load a file that does not exist.
+      \endcode
+
+      - \b CImgWarningException : Thrown only if configuration macro \c cimg_strict_warnings is set, and
+      when a \CImg function has to display a warning message (see cimg::warn()).
+
+      It is not recommended to throw CImgException instances by yourself, since they are expected to be thrown only by \CImg.
+      When an error occurs in a library function call, \CImg may display error messages on the screen or on the
+      standard output, depending on the current \CImg exception mode.
+      The \CImg exception mode can be get and set by functions cimg::exception_mode() and cimg::exception_mode(unsigned int).
+
+      \par Exceptions handling
+
+      In all cases, when an error occurs in \CImg, an instance of the corresponding exception class is thrown.
+      This may lead the program to break (this is the default behavior), but you can bypass this behavior by handling the exceptions by yourself,
+      using a usual <tt>try { ... } catch () { ... }</tt> bloc, as in the following example :
+      \code
+      #define "CImg.h"
+      using namespace cimg_library;
+      int main() {
+        cimg::exception_mode(0);                                    // Enable quiet exception mode.
+        try {
+          ...                                                       // Here, do what you want to stress the CImg library.
+        } catch (CImgException &e) {                                // You succeeded : something went wrong !
+          std::fprintf(stderr,"CImg Library Error : %s",e.what());  // Display your custom error message.
+          ...                                                       // Do what you want now to save the ship !
+          }
+        }
+      \endcode
+  **/
+  struct CImgException : public std::exception {
+#define _cimg_exception_err(etype,disp_flag) \
+  std::va_list ap; va_start(ap,format); cimg_vsnprintf(_message,sizeof(_message),format,ap); va_end(ap); \
+  if (cimg::exception_mode()) { \
+    std::fprintf(cimg::output(),"\n%s[CImg] *** %s ***%s %s\n",cimg::t_red,etype,cimg::t_normal,_message); \
+    if (cimg_display && disp_flag && !(cimg::exception_mode()%2)) try { cimg::dialog(etype,_message,"Abort"); } catch (CImgException&) {} \
+    if (cimg::exception_mode()>=3) cimg_library_suffixed::cimg::info(); \
+  }
+
+    char _message[16384];
+    CImgException() { *_message = 0; }
+    CImgException(const char *const format, ...) { _cimg_exception_err("CImgException",true); }
+    //! Return a C-string containing the error message associated to the thrown exception.
+    const char *what() const throw() { return _message; }
+  };
+
+  // The CImgInstanceException class is used to throw an exception related
+  // to an invalid instance encountered in a library function call.
+  struct CImgInstanceException : public CImgException {
+    CImgInstanceException(const char *const format, ...) { _cimg_exception_err("CImgInstanceException",true); }
+  };
+
+  // The 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 *const format, ...) { _cimg_exception_err("CImgArgumentException",true); }
+  };
+
+  // The 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 *const 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 *const 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 *const format, ...) { _cimg_exception_err("CImgWarningException",false); }
+  };
+
+  /*-------------------------------------
+    #
+    # Define cimg:: namespace
+    #
+    -----------------------------------*/
+  //! Contains \a low-level functions and variables of the \CImg Library.
+  /**
+     Most of the functions and variables within this namespace are used by the \CImg library for low-level operations.
+     You may use them to access specific const values or environment variables internally used by \CImg.
+     \warning Never write <tt>using namespace cimg_library::cimg;</tt> in your source code. Lot of functions in the
+     <tt>cimg:: namespace</tt> have the same names as standard C functions that may be defined in the global namespace <tt>::</tt>.
+  **/
+  namespace cimg {
+
+    // Define traits that will be used to determine the best data type to work in CImg functions.
+    //
+    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 bool is_inf(const T) { return false; }
+      static bool is_nan(const T) { 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 T inf() { return max(); }
+      static T cut(const double val) { return val<(double)min()?min():val>(double)max()?max():(T)val; }
+      static const char* format() { return "%s"; }
+      static const char* format(const T val) { static const char *const s = "unknown"; cimg::unused(val); 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 is_inf(const bool) { return false; }
+      static bool is_nan(const bool) { return false; }
+      static bool min() { return false; }
+      static bool max() { return true; }
+      static bool inf() { return max(); }
+      static bool is_inf() { return false; }
+      static bool cut(const double val) { return val<(double)min()?min():val>(double)max()?max():(bool)val; }
+      static const char* format() { return "%s"; }
+      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 bool is_inf(const unsigned char) { return false; }
+      static bool is_nan(const unsigned char) { return false; }
+      static unsigned char min() { return 0; }
+      static unsigned char max() { return (unsigned char)~0U; }
+      static unsigned char inf() { return max(); }
+      static unsigned char cut(const double val) { return val<(double)min()?min():val>(double)max()?max():(unsigned char)val; }
+      static const char* format() { return "%u"; }
+      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 bool is_inf(const char) { return false; }
+      static bool is_nan(const char) { return false; }
+      static char min() { return (char)(-1L<<(8*sizeof(char)-1)); }
+      static char max() { return ~((char)(-1L<<(8*sizeof(char)-1))); }
+      static char inf() { return max(); }
+      static char cut(const double val) { return val<(double)min()?min():val>(double)max()?max():(char)val; }
+      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 bool is_inf(const signed char) { return false; }
+      static bool is_nan(const signed char) { 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 signed char inf() { return max(); }
+      static signed char cut(const double val) { return val<(double)min()?min():val>(double)max()?max():(signed char)val; }
+      static const char* format() { return "%d"; }
+      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 bool is_inf(const unsigned short) { return false; }
+      static bool is_nan(const unsigned short) { return false; }
+      static unsigned short min() { return 0; }
+      static unsigned short max() { return (unsigned short)~0U; }
+      static unsigned short inf() { return max(); }
+      static unsigned short cut(const double val) { return val<(double)min()?min():val>(double)max()?max():(unsigned short)val; }
+      static const char* format() { return "%u"; }
+      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 bool is_inf(const short) { return false; }
+      static bool is_nan(const short) { return false; }
+      static short min() { return (short)(-1L<<(8*sizeof(short)-1)); }
+      static short max() { return ~((short)(-1L<<(8*sizeof(short)-1))); }
+      static short inf() { return max(); }
+      static short cut(const double val) { return val<(double)min()?min():val>(double)max()?max():(short)val; }
+      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 bool is_inf(const unsigned int) { return false; }
+      static bool is_nan(const unsigned int) { return false; }
+      static unsigned int min() { return 0; }
+      static unsigned int max() { return (unsigned int)~0U; }
+      static unsigned int inf() { return max(); }
+      static unsigned int cut(const double val) { return val<(double)min()?min():val>(double)max()?max():(unsigned int)val; }
+      static const char* format() { return "%u"; }
+      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 bool is_inf(const int) { return false; }
+      static bool is_nan(const int) { return false; }
+      static int min() { return (int)(-1L<<(8*sizeof(int)-1)); }
+      static int max() { return ~((int)(-1L<<(8*sizeof(int)-1))); }
+      static int inf() { return max(); }
+      static int cut(const double val) { return val<(double)min()?min():val>(double)max()?max():(int)val; }
+      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 bool is_inf(const unsigned long) { return false; }
+      static bool is_nan(const unsigned long) { return false; }
+      static unsigned long min() { return 0; }
+      static unsigned long max() { return (unsigned long)~0UL; }
+      static unsigned long inf() { return max(); }
+      static unsigned long cut(const double val) { return val<(double)min()?min():val>(double)max()?max():(unsigned long)val; }
+      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 bool is_inf(const long) { return false; }
+      static bool is_nan(const long) { return false; }
+      static long min() { return (long)(-1L<<(8*sizeof(long)-1)); }
+      static long max() { return ~((long)(-1L<<(8*sizeof(long)-1))); }
+      static long inf() { return max(); }
+      static long cut(const double val) { return val<(double)min()?min():val>(double)max()?max():(long)val; }
+      static const char* format() { return "%ld"; }
+      static long format(const long val) { return val; }
+    };
+
+    template<> struct type<double> {
+      static const char* string() { static const char *const s = "double"; return s; }
+      static bool is_float() { return true; }
+      static bool is_inf(const double val) { return !is_nan(val) && val+1==val; }
+      static bool is_nan(const double val) { return !(val<=0 || val>=0); }
+      static double min() { return -1.7E308; }
+      static double max() { return  1.7E308; }
+      static double inf() { return max()*max(); }
+      static double nan() { static const double val_nan = -std::sqrt(-1.0); return val_nan; }
+      static double cut(const double val) { return val<min()?min():val>max()?max():val; }
+      static const char* format() { return "%g"; }
+      static double format(const double 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 bool is_inf(const float val) { return cimg::type<double>::is_inf((double)val); }
+      static bool is_nan(const float val) { return cimg::type<double>::is_nan((double)val); }
+      static float min() { return -3.4E38f; }
+      static float max() { return  3.4E38f; }
+      static float inf() { return (float)cimg::type<double>::inf(); }
+      static float nan() { return (float)cimg::type<double>::nan(); }
+      static float cut(const double val) { return val<(double)min()?min():val>(double)max()?max():(float)val; }
+      static const char* format() { return "%g"; }
+      static double format(const float val) { return (double)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_Tt      typename cimg::superset<T,t>::type
+#define _cimg_Tfloat  typename cimg::superset<T,float>::type
+#define _cimg_Ttfloat typename cimg::superset2<T,t,float>::type
+
+    // Define variables used internally by CImg.
+#if cimg_display==1
+    struct X11_info {
+      volatile unsigned int nb_wins;
+      pthread_t*       event_thread;
+      CImgDisplay*     wins[1024];
+      Display*         display;
+      unsigned int     nb_bits;
+      bool             is_blue_first;
+      bool             is_shm_enabled;
+      bool             byte_order;
+#ifdef cimg_use_xrandr
+      XRRScreenSize *resolutions;
+      Rotation curr_rotation;
+      unsigned int curr_resolution;
+      unsigned int nb_resolutions;
+#endif
+      X11_info():nb_wins(0),event_thread(0),display(0),
+                 nb_bits(0),is_blue_first(false),is_shm_enabled(false),byte_order(false) {
+#ifdef cimg_use_xrandr
+        resolutions = 0;
+        curr_rotation = 0;
+        curr_resolution = nb_resolutions = 0;
+#endif
+      }
+    };
+#if defined(cimg_module)
+    X11_info& X11_attr();
+#elif defined(cimg_main)
+    X11_info& X11_attr() { static X11_info val; return val; }
+#else
+    inline X11_info& X11_attr() { static X11_info val; return val; }
+#endif
+
+#elif cimg_display==2
+    struct Win32_info {
+      HANDLE wait_event;
+      Win32_info() { wait_event = CreateEvent(0,FALSE,FALSE,0); }
+    };
+#if defined(cimg_module)
+    Win32_info& Win32_attr();
+#elif defined(cimg_main)
+    Win32_info& Win32_attr() { static Win32_info val; return val; }
+#else
+    inline Win32_info& Win32_attr() { static Win32_info val; return val; }
+#endif
+#endif
+
+#if defined(cimg_use_magick)
+    static struct Magick_info {
+      Magick_info() {
+        Magick::InitializeMagick("");
+      }
+    } _Magick_info;
+#endif
+
+#if cimg_display==1
+    // Define 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
+    // Define 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;
+
+#else
+    // Define random keycodes when no display is available.
+    // (should rarely be used then !).
+    const unsigned int keyESC        = 1U;   //!< Keycode for the \c ESC key (architecture-dependent).
+    const unsigned int keyF1         = 2U;   //!< Keycode for the \c F1 key (architecture-dependent).
+    const unsigned int keyF2         = 3U;   //!< Keycode for the \c F2 key (architecture-dependent).
+    const unsigned int keyF3         = 4U;   //!< Keycode for the \c F3 key (architecture-dependent).
+    const unsigned int keyF4         = 5U;   //!< Keycode for the \c F4 key (architecture-dependent).
+    const unsigned int keyF5         = 6U;   //!< Keycode for the \c F5 key (architecture-dependent).
+    const unsigned int keyF6         = 7U;   //!< Keycode for the \c F6 key (architecture-dependent).
+    const unsigned int keyF7         = 8U;   //!< Keycode for the \c F7 key (architecture-dependent).
+    const unsigned int keyF8         = 9U;   //!< Keycode for the \c F8 key (architecture-dependent).
+    const unsigned int keyF9         = 10U;  //!< Keycode for the \c F9 key (architecture-dependent).
+    const unsigned int keyF10        = 11U;  //!< Keycode for the \c F10 key (architecture-dependent).
+    const unsigned int keyF11        = 12U;  //!< Keycode for the \c F11 key (architecture-dependent).
+    const unsigned int keyF12        = 13U;  //!< Keycode for the \c F12 key (architecture-dependent).
+    const unsigned int keyPAUSE      = 14U;  //!< Keycode for the \c PAUSE key (architecture-dependent).
+    const unsigned int key1          = 15U;  //!< Keycode for the \c 1 key (architecture-dependent).
+    const unsigned int key2          = 16U;  //!< Keycode for the \c 2 key (architecture-dependent).
+    const unsigned int key3          = 17U;  //!< Keycode for the \c 3 key (architecture-dependent).
+    const unsigned int key4          = 18U;  //!< Keycode for the \c 4 key (architecture-dependent).
+    const unsigned int key5          = 19U;  //!< Keycode for the \c 5 key (architecture-dependent).
+    const unsigned int key6          = 20U;  //!< Keycode for the \c 6 key (architecture-dependent).
+    const unsigned int key7          = 21U;  //!< Keycode for the \c 7 key (architecture-dependent).
+    const unsigned int key8          = 22U;  //!< Keycode for the \c 8 key (architecture-dependent).
+    const unsigned int key9          = 23U;  //!< Keycode for the \c 9 key (architecture-dependent).
+    const unsigned int key0          = 24U;  //!< Keycode for the \c 0 key (architecture-dependent).
+    const unsigned int keyBACKSPACE  = 25U;  //!< Keycode for the \c BACKSPACE key (architecture-dependent).
+    const unsigned int keyINSERT     = 26U;  //!< Keycode for the \c INSERT key (architecture-dependent).
+    const unsigned int keyHOME       = 27U;  //!< Keycode for the \c HOME key (architecture-dependent).
+    const unsigned int keyPAGEUP     = 28U;  //!< Keycode for the \c PAGEUP key (architecture-dependent).
+    const unsigned int keyTAB        = 29U;  //!< Keycode for the \c TAB key (architecture-dependent).
+    const unsigned int keyQ          = 30U;  //!< Keycode for the \c Q key (architecture-dependent).
+    const unsigned int keyW          = 31U;  //!< Keycode for the \c W key (architecture-dependent).
+    const unsigned int keyE          = 32U;  //!< Keycode for the \c E key (architecture-dependent).
+    const unsigned int keyR          = 33U;  //!< Keycode for the \c R key (architecture-dependent).
+    const unsigned int keyT          = 34U;  //!< Keycode for the \c T key (architecture-dependent).
+    const unsigned int keyY          = 35U;  //!< Keycode for the \c Y key (architecture-dependent).
+    const unsigned int keyU          = 36U;  //!< Keycode for the \c U key (architecture-dependent).
+    const unsigned int keyI          = 37U;  //!< Keycode for the \c I key (architecture-dependent).
+    const unsigned int keyO          = 38U;  //!< Keycode for the \c O key (architecture-dependent).
+    const unsigned int keyP          = 39U;  //!< Keycode for the \c P key (architecture-dependent).
+    const unsigned int keyDELETE     = 40U;  //!< Keycode for the \c DELETE key (architecture-dependent).
+    const unsigned int keyEND        = 41U;  //!< Keycode for the \c END key (architecture-dependent).
+    const unsigned int keyPAGEDOWN   = 42U;  //!< Keycode for the \c PAGEDOWN key (architecture-dependent).
+    const unsigned int keyCAPSLOCK   = 43U;  //!< Keycode for the \c CAPSLOCK key (architecture-dependent).
+    const unsigned int keyA          = 44U;  //!< Keycode for the \c A key (architecture-dependent).
+    const unsigned int keyS          = 45U;  //!< Keycode for the \c S key (architecture-dependent).
+    const unsigned int keyD          = 46U;  //!< Keycode for the \c D key (architecture-dependent).
+    const unsigned int keyF          = 47U;  //!< Keycode for the \c F key (architecture-dependent).
+    const unsigned int keyG          = 48U;  //!< Keycode for the \c G key (architecture-dependent).
+    const unsigned int keyH          = 49U;  //!< Keycode for the \c H key (architecture-dependent).
+    const unsigned int keyJ          = 50U;  //!< Keycode for the \c J key (architecture-dependent).
+    const unsigned int keyK          = 51U;  //!< Keycode for the \c K key (architecture-dependent).
+    const unsigned int keyL          = 52U;  //!< Keycode for the \c L key (architecture-dependent).
+    const unsigned int keyENTER      = 53U;  //!< Keycode for the \c ENTER key (architecture-dependent).
+    const unsigned int keySHIFTLEFT  = 54U;  //!< Keycode for the \c SHIFTLEFT key (architecture-dependent).
+    const unsigned int keyZ          = 55U;  //!< Keycode for the \c Z key (architecture-dependent).
+    const unsigned int keyX          = 56U;  //!< Keycode for the \c X key (architecture-dependent).
+    const unsigned int keyC          = 57U;  //!< Keycode for the \c C key (architecture-dependent).
+    const unsigned int keyV          = 58U;  //!< Keycode for the \c V key (architecture-dependent).
+    const unsigned int keyB          = 59U;  //!< Keycode for the \c B key (architecture-dependent).
+    const unsigned int keyN          = 60U;  //!< Keycode for the \c N key (architecture-dependent).
+    const unsigned int keyM          = 61U;  //!< Keycode for the \c M key (architecture-dependent).
+    const unsigned int keySHIFTRIGHT = 62U;  //!< Keycode for the \c SHIFTRIGHT key (architecture-dependent).
+    const unsigned int keyARROWUP    = 63U;  //!< Keycode for the \c ARROWUP key (architecture-dependent).
+    const unsigned int keyCTRLLEFT   = 64U;  //!< Keycode for the \c CTRLLEFT key (architecture-dependent).
+    const unsigned int keyAPPLEFT    = 65U;  //!< Keycode for the \c APPLEFT key (architecture-dependent).
+    const unsigned int keyALT        = 66U;  //!< Keycode for the \c ALT key (architecture-dependent).
+    const unsigned int keySPACE      = 67U;  //!< Keycode for the \c SPACE key (architecture-dependent).
+    const unsigned int keyALTGR      = 68U;  //!< Keycode for the \c ALTGR key (architecture-dependent).
+    const unsigned int keyAPPRIGHT   = 69U;  //!< Keycode for the \c APPRIGHT key (architecture-dependent).
+    const unsigned int keyMENU       = 70U;  //!< Keycode for the \c MENU key (architecture-dependent).
+    const unsigned int keyCTRLRIGHT  = 71U;  //!< Keycode for the \c CTRLRIGHT key (architecture-dependent).
+    const unsigned int keyARROWLEFT  = 72U;  //!< Keycode for the \c ARROWLEFT key (architecture-dependent).
+    const unsigned int keyARROWDOWN  = 73U;  //!< Keycode for the \c ARROWDOWN key (architecture-dependent).
+    const unsigned int keyARROWRIGHT = 74U;  //!< Keycode for the \c ARROWRIGHT key (architecture-dependent).
+    const unsigned int keyPAD0       = 75U;  //!< Keycode for the \c PAD0 key (architecture-dependent).
+    const unsigned int keyPAD1       = 76U;  //!< Keycode for the \c PAD1 key (architecture-dependent).
+    const unsigned int keyPAD2       = 77U;  //!< Keycode for the \c PAD2 key (architecture-dependent).
+    const unsigned int keyPAD3       = 78U;  //!< Keycode for the \c PAD3 key (architecture-dependent).
+    const unsigned int keyPAD4       = 79U;  //!< Keycode for the \c PAD4 key (architecture-dependent).
+    const unsigned int keyPAD5       = 80U;  //!< Keycode for the \c PAD5 key (architecture-dependent).
+    const unsigned int keyPAD6       = 81U;  //!< Keycode for the \c PAD6 key (architecture-dependent).
+    const unsigned int keyPAD7       = 82U;  //!< Keycode for the \c PAD7 key (architecture-dependent).
+    const unsigned int keyPAD8       = 83U;  //!< Keycode for the \c PAD8 key (architecture-dependent).
+    const unsigned int keyPAD9       = 84U;  //!< Keycode for the \c PAD9 key (architecture-dependent).
+    const unsigned int keyPADADD     = 85U;  //!< Keycode for the \c PADADD key (architecture-dependent).
+    const unsigned int keyPADSUB     = 86U;  //!< Keycode for the \c PADSUB key (architecture-dependent).
+    const unsigned int keyPADMUL     = 87U;  //!< Keycode for the \c PADMUL key (architecture-dependent).
+    const unsigned int keyPADDIV     = 88U;  //!< Keycode for the \c PADDDIV key (architecture-dependent).
+#endif
+
+    const double PI = 3.14159265358979323846;   //!< Value of the mathematical constant PI
+
+    // Define a 10x13 font (small size).
+    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
+    };
+
+    // Define a 12x24 font (normal size).
+    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 };
+
+    // Define a 16x32 font (large size).
+    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 };
+
+    // Define a 29x57 font (extra large size).
+    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 };
+
+    // Define a 40x38 'danger' color logo (used by cimg::dialog()).
+    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};
+
+    //! Get/set default output stream for the \CImg library messages.
+    /**
+       \param file Desired output stream. Set to \c 0 to get the currently used output stream only.
+       \return Currently used output stream.
+       \see info(), warn(), CImgException.
+    **/
+    inline std::FILE* output(std::FILE *file) {
+      static std::FILE *res = stderr;
+      if (file) res = file;
+      return res;
+    }
+
+    //! Display a warning message on the default output stream.
+    /**
+       \param format C-string containing the format of the message, as with <tt>std::printf()</tt>.
+       \note If configuration macro \c cimg_strict_warnings is set, this function throws a \c CImgWarningException instead.
+       \warning As the first argument is a format string, it is highly recommended to write
+       \code
+       cimg::warn("%s",warning_message);
+       \endcode
+       instead of
+       \code
+       cimg::warn(warning_message);
+       \endcode
+       if \c warning_message can be arbitrary, to prevent nasty memory access.
+       \see output(), info(), CImgException.
+    **/
+    inline void warn(const char *const format, ...) {
+      if (cimg::exception_mode()>=1) {
+        char message[16384] = { 0 };
+        std::va_list ap;
+        va_start(ap,format);
+        cimg_vsnprintf(message,sizeof(message),format,ap);
+        va_end(ap);
+#ifdef cimg_strict_warnings
+        throw CImgWarningException(message);
+#else
+        std::fprintf(cimg::output(),"\n%s[CImg] *** Warning ***%s%s",cimg::t_red,cimg::t_normal,message);
+#endif
+      }
+    }
+
+    // Execute an external system command.
+    /**
+       \param command C-string containing the command line to execute.
+       \return Status value of the executed command, whose meaning is OS-dependent.
+       \note This function is similar to <tt>std::system()</tt>
+       but it does not open an extra console windows
+       on Windows-based systems.
+    **/
+    inline int system(const char *const command, const char *const module_name=0) {
+#if cimg_OS==2
+      PROCESS_INFORMATION pi;
+      STARTUPINFO si;
+      std::memset(&pi,0,sizeof(PROCESS_INFORMATION));
+      std::memset(&si,0,sizeof(STARTUPINFO));
+      GetStartupInfo(&si);
+      si.cb = sizeof(si);
+      si.wShowWindow = SW_HIDE;
+      si.dwFlags |= SW_HIDE | STARTF_USESHOWWINDOW;
+      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 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 \c a and \c b.
+    template<typename T>
+    inline void swap(T& a, T& b) { T t = a; a = b; b = t; }
+
+    //! Exchange values of variables (\c a1,\c a2) and (\c b1,\c 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 (\c a1,\c a2,\c a3) and (\c b1,\c b2,\c 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 (\c a1,\c a2,...,\c a4) and (\c b1,\c b2,...,\c 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 (\c a1,\c a2,...,\c a5) and (\c b1,\c b2,...,\c 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 (\c a1,\c a2,...,\c a6) and (\c b1,\c b2,...,\c 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 (\c a1,\c a2,...,\c a7) and (\c b1,\c b2,...,\c 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 (\c a1,\c a2,...,\c a8) and (\c b1,\c b2,...,\c 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 endianness of the current architecture.
+    /**
+       \return \c false for <i>Little Endian</i> or \c true for <i>Big Endian</i>.
+       \see invert_endianness(T*,unsigned int), invert_endianness(T&),
+    **/
+    inline bool endianness() {
+      const int x = 1;
+      return ((unsigned char*)&x)[0]?false:true;
+    }
+
+    //! Reverse endianness of all elements in a memory buffer.
+    /**
+       \param[in,out] buffer Memory buffer whose endianness must be reversed.
+       \param size Number of buffer elements to reverse.
+       \see endianness(), invert_endianness(T&).
+    **/
+    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));
+      }
+      }
+      }
+    }
+
+    //! Reverse endianness of a single variable.
+    /**
+       \param[in,out] a Variable to reverse.
+       \return Reference to reversed variable.
+       \see endianness(), invert_endianness(T*,unsigned int).
+    **/
+    template<typename T>
+    inline T& invert_endianness(T& a) {
+      invert_endianness(&a,1);
+      return a;
+    }
+
+    // Conversion functions to get more precision when trying to store unsigned ints values as floats.
+    inline unsigned int float2uint(const float f) {
+      int tmp = 0;
+      std::memcpy(&tmp,&f,sizeof(float));
+      if (tmp>=0) return (unsigned int)f;
+      unsigned int u;
+      std::memcpy(&u,&f,sizeof(float));  // use memcpy instead of assignment to avoid undesired optimizations by C++-compiler.
+      return ((u)<<1)>>1; // set sign bit to 0.
+    }
+
+    inline float uint2float(const unsigned int u) {
+      if (u<(1U<<19)) return (float)u;  // Consider safe storage of unsigned int as floats until 19bits (i.e 524287).
+      float f;
+      const unsigned int v = u|(1U<<(8*sizeof(unsigned int)-1)); // set sign bit to 1.
+      std::memcpy(&f,&v,sizeof(float)); // use memcpy instead of simple assignment to avoid undesried optimizations by C++-compiler.
+      return f;
+    }
+
+    //! Return the value of a system timer, with a millisecond precision.
+    /**
+       \note The timer does not necessarily starts from \c 0.
+       \see tic(), toc(), sleep(), wait().
+    **/
+    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
+      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
+    }
+
+    // Implement a tic/toc mechanism to display elapsed time of algorithms.
+    inline unsigned long tictoc(const bool is_tic) {
+      static unsigned long t0 = 0;
+      const unsigned long t = cimg::time();
+      if (is_tic) return (t0 = t);
+      const unsigned long dt = t>=t0?(t - t0):cimg::type<unsigned long>::max();
+      const unsigned int
+        edays = (unsigned int)(dt/86400000.0),
+        ehours = (unsigned int)((dt - edays*86400000.0)/3600000.0),
+        emin = (unsigned int)((dt - edays*86400000.0 - ehours*3600000.0)/60000.0),
+        esec = (unsigned int)((dt - edays*86400000.0 - ehours*3600000.0 - emin*60000.0)/1000.0),
+        ems = (unsigned int)(dt - edays*86400000.0 - ehours*3600000.0 - emin*60000.0 - esec*1000.0);
+      if (!edays && !ehours && !emin && !esec)
+        std::fprintf(cimg::output(),"%s[CImg] Elapsed time : %u ms%s\n",cimg::t_red,ems,cimg::t_normal);
+      else {
+        if (!edays && !ehours && !emin)
+          std::fprintf(cimg::output(),"%s[CImg] Elapsed time : %u sec %u ms%s\n",cimg::t_red,esec,ems,cimg::t_normal);
+        else {
+          if (!edays && !ehours)
+            std::fprintf(cimg::output(),"%s[CImg] Elapsed time : %u min %u sec %u ms%s\n",cimg::t_red,emin,esec,ems,cimg::t_normal);
+          else{
+            if (!edays)
+              std::fprintf(cimg::output(),"%s[CImg] Elapsed time : %u hours %u min %u sec %u ms%s\n",cimg::t_red,ehours,emin,esec,ems,cimg::t_normal);
+            else{
+              std::fprintf(cimg::output(),"%s[CImg] Elapsed time : %u days %u hours %u min %u sec %u ms%s\n",cimg::t_red,edays,ehours,emin,esec,ems,cimg::t_normal);
+            }
+          }
+        }
+      }
+      return t;
+    }
+
+    //! Start tic/toc timer for time measurement between code instructions.
+    /**
+       \return Current value of the timer (same value as time()).
+       \see time(), toc(), sleep(), wait().
+    **/
+    inline unsigned long tic() {
+      return cimg::tictoc(true);
+    }
+
+    //! End tic/toc timer and displays elapsed time from last call to tic().
+    /**
+       \return Time elapsed (in ms) since last call to tic().
+       \see time(), tic(), sleep(), wait().
+    **/
+    inline unsigned long toc() {
+      return cimg::tictoc(false);
+    }
+
+    //! Sleep for a given numbers of milliseconds.
+    /**
+       \param milliseconds Number of milliseconds to wait for.
+       \note This function frees the CPU ressources during the sleeping time.
+       It can be used to temporize your program properly, without wasting CPU time.
+       \see time(), tic(), toc(), wait().
+    **/
+    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 _wait(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 given number of milliseconds since the last call to wait().
+    /**
+       \param milliseconds Number of milliseconds to wait for.
+       \return Number of milliseconds elapsed since the last call to wait().
+       \note Similar 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, without wasting CPU time.
+       \see time(), tic(), toc(), sleep().
+    **/
+    inline unsigned int wait(const unsigned int milliseconds) {
+      static unsigned long timer = 0;
+      if (!timer) timer = cimg::time();
+      return _wait(milliseconds,timer);
+    }
+
+    // Random number generators.
+    // CImg may use its own Random Number Generator (RNG) if configuration macro 'cimg_use_rng' is set.
+    // Use it for instance when you have to deal with concurrent threads trying to call std::srand()
+    // at the same time !
+#ifdef cimg_use_rng
+
+    // Use a custom RNG.
+    inline unsigned int _rand(const unsigned long seed=0, const bool set_seed=false) {
+      static unsigned long next = 1;
+      if (set_seed) next = seed;
+      next = next*1103515245 + 12345;
+      return (unsigned int)((next>>16)&0x7FFF);
+    }
+
+    inline void srand() {
+      static bool is_first_time = true;
+      if (is_first_time) {
+        const unsigned long t = cimg::time();
+#if cimg_OS==1
+        cimg::_rand(t+(unsigned long)getpid(),true);
+#elif cimg_OS==2
+        cimg::_rand(t+(unsigned long)_getpid(),true);
+#else
+        cimg::_rand(t,true);
+#endif
+        is_first_time = false;
+      }
+    }
+
+    inline double rand() {
+      cimg::srand();
+      return cimg::_rand()/32767.;
+    }
+
+#else
+
+    // Use the system RNG.
+    inline void srand() {
+      static bool is_first_time = true;
+      if (is_first_time) {
+        const unsigned int t = (unsigned int)cimg::time();
+#if cimg_OS==1
+        std::srand(t+(unsigned int)getpid());
+#elif cimg_OS==2
+        std::srand(t+(unsigned int)_getpid());
+#else
+        std::srand(t);
+#endif
+        is_first_time = false;
+      }
+    }
+
+    //! Return a random variable between [0,1] with respect to an uniform distribution.
+    /**
+       \see crand(), grand(), prand().
+    **/
+    inline double rand() {
+      cimg::srand();
+      return (double)std::rand()/RAND_MAX;
+    }
+#endif
+
+    //! Return a random variable between [-1,1] with respect to an uniform distribution.
+    /**
+       \see rand(), grand(), prand().
+    **/
+    inline double crand() {
+      return 1-2*cimg::rand();
+    }
+
+    //! Return a random variable following a gaussian distribution and a standard deviation of 1.
+    /**
+       \see rand(), crand(), prand().
+    **/
+    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*std::sqrt((-2*std::log(w))/w);
+    }
+
+    //! Return a random variable following a Poisson distribution of parameter z.
+    /**
+       \see rand(), crand(), grand().
+    **/
+    inline unsigned int prand(const double z) {
+      if (z<=1.0e-10) return 0;
+      if (z>100) 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;
+    }
+
+    //! Bitwise-rotate value on the left.
+    template<typename T>
+    inline T rol(const T a, const unsigned int n=1) {
+      return n?(T)((a<<n)|(a>>((sizeof(T)<<3)-n))):a;
+    }
+
+    inline float rol(const float a, const unsigned int n=1) {
+      return (float)rol((int)a,n);
+    }
+
+    inline double rol(const double a, const unsigned int n=1) {
+      return (double)rol((long)a,n);
+    }
+
+    //! Bitwise-rotate value on the right.
+    template<typename T>
+    inline T ror(const T a, const unsigned int n=1) {
+      return n?(T)((a>>n)|(a<<((sizeof(T)<<3)-n))):a;
+    }
+
+    inline float ror(const float a, const unsigned int n=1) {
+      return (float)ror((int)a,n);
+    }
+
+    inline double ror(const double a, const unsigned int n=1) {
+      return (double)ror((long)a,n);
+    }
+
+    //! Return absolute value of a value.
+    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 std::fabs(a);
+    }
+    inline float abs(const float a) {
+      return (float)std::fabs((double)a);
+    }
+    inline int abs(const int a) {
+      return std::abs(a);
+    }
+
+    //! Return square of a value.
+    template<typename T>
+    inline T sqr(const T val) {
+      return val*val;
+    }
+
+    //! Return <tt>1 + log_10(x)</tt> of a value \c x.
+    inline int xln(const int x) {
+      return x>0?(int)(1+std::log10((double)x)):1;
+    }
+
+    //! Return the minimum between two values.
+    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 between three values.
+    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 between four values.
+    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 between two values.
+    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 between three values.
+    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 between four values.
+    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 value.
+    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 given value.
+    template<typename T>
+    inline unsigned int nearest_pow2(const T x) {
+      unsigned int i = 1;
+      while (x>i) i<<=1;
+      return i;
+    }
+
+    //! Return the sinc of a given value.
+    inline double sinc(const double x) {
+      return x?std::sin(x)/x:1;
+    }
+
+    //! Return the modulo of a value.
+    /**
+       \param x Input value.
+       \param m Modulo value.
+       \note This modulo function accepts negative and floating-points modulo numbers, as well as variables of any type.
+    **/
+    template<typename T>
+    inline T mod(const T& x, const T& m) {
+      const double dx = (double)x, dm = (double)m;
+      return (T)(dx - dm * 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 min-mod of two values.
+    /**
+       \note <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 base-2 logarithm of a value.
+    inline double log2(const double x) {
+      static const double base = std::log(2.0);
+      return std::log(x)/base;
+    }
+
+    //! Return rounded value.
+    /**
+       \param x Value to be rounded.
+       \param y Rounding precision.
+       \param rounding_type Type of rounding operation (\c 0 = nearest, \c -1 = backward, \c 1 = forward).
+       \return Rounded value, having the same type as input value \c x.
+    **/
+    template<typename T>
+    inline T round(const T x, const double y=1, const int rounding_type=0) {
+      if (y<=0) return x;
+      const double delta = cimg::mod((double)x,y);
+      if (delta==0.0) return x;
+      const double backward = x - delta, forward = backward + y;
+      return (T)(rounding_type<0?backward:rounding_type>0?forward:2*delta<y?backward:2*delta>y?forward:x<0?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*std::sqrt(1.0+tmp*tmp); }
+      else { const double tmp = absa/absb; return absb==0?0:absb*std::sqrt(1.0+tmp*tmp); }
+    }
+
+    //! Convert ascii character to lower case.
+    inline char uncase(const char x) {
+      return (char)((x<'A'||x>'Z')?x:x-'A'+'a');
+    }
+
+    //! Convert C-string to lower case.
+    inline void uncase(char *const str) {
+      if (str) for (char *ptr = str; *ptr; ++ptr) *ptr = uncase(*ptr);
+    }
+
+    //! Read value in a C-string.
+    /**
+       \param str C-string containing the float value to read.
+       \return Read value.
+       \note Similar to <tt>std::atof()</tt>, but allows the retrieval of fractions from C-strings, as in <em>"1/2"</em>.
+    **/
+    inline double atof(const char *const str) {
+      double x = 0, y = 1;
+      if (!str) return 0; else { std::sscanf(str,"%lf/%lf",&x,&y); return x/y; }
+    }
+
+    //! Compare the first \p l characters of two C-strings, ignoring the case.
+    /**
+       \param str1 C-string.
+       \param str2 C-string.
+       \param l Number of characters to compare.
+       \return \c 0 if the two strings are equal, something else otherwise.
+       \note This function has to be defined since it is not provided by all C++-compilers (not ANSI).
+       \see strcasecmp().
+    **/
+    inline int strncasecmp(const char *const str1, const char *const str2, const int l) {
+      if (!l) return 0;
+      if (!str1) return str2?-1:0;
+      const char *nstr1 = str1, *nstr2 = str2;
+      int k, diff = 0; for (k = 0; k<l && !(diff = uncase(*nstr1)-uncase(*nstr2)); ++k) { ++nstr1; ++nstr2; }
+      return k!=l?diff:0;
+    }
+
+    //! Compare two C-strings, ignoring the case.
+    /**
+       \param str1 C-string.
+       \param str2 C-string.
+       \return \c 0 if the two strings are equal, something else otherwise.
+       \note This function has to be defined since it is not provided by all C++-compilers (not ANSI).
+       \see strncasecmp().
+    **/
+    inline int strcasecmp(const char *const str1, const char *const str2) {
+      if (!str1) return str2?-1:0;
+      const unsigned int l1 = std::strlen(str1), l2 = std::strlen(str2);
+      return cimg::strncasecmp(str1,str2,1+(l1<l2?l1:l2));
+    }
+
+    //! Remove delimiters on the start and/or end of a C-string.
+    /**
+       \param[in,out] str C-string to work with (modified at output).
+       \param delimiter Delimiter character code to remove.
+       \param is_symmetric Flag telling if the removal is done only if delimiters are symmetric (both at the beginning and the end of \c s).
+       \param is_iterative Flag telling if the removal is done if several iterations are possible.
+       \return \c true if delimiters have been removed, \c false otherwise.
+       \see strescape().
+   **/
+    inline bool strpare(char *const str, const char delimiter=' ', const bool is_symmetric=false, const bool is_iterative=false) {
+      if (!str) return false;
+      const int l = (int)std::strlen(str);
+      int p, q;
+      if (is_symmetric) for (p = 0, q = l-1; p<q && str[p]==delimiter && str[q]==delimiter; ) { --q; ++p; if (!is_iterative) break; }
+      else {
+        for (p = 0; p<l && str[p]==delimiter; ) { ++p; if (!is_iterative) break; }
+        for (q = l-1; q>p && str[q]==delimiter; ) { --q; if (!is_iterative) break; }
+      }
+      const int n = q - p + 1;
+      if (n!=l) { std::memmove(str,str+p,n); str[n] = 0; return true; }
+      return false;
+    }
+
+    //! Replace escape sequences in C-strings by their binary ascii values.
+    /**
+       \param[in,out] str C-string to work with (modified at output).
+       \see strpare().
+     **/
+    inline void strescape(char *const str) {
+#define cimg_strescape(ci,co) case ci: *nd = co; ++ns; break;
+      static unsigned int val = 0;
+      for (char *ns = str, *nd = str; *ns || (bool)(*nd=0); ++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('\"','\"');
+          case 0 : *nd = 0; break;
+          case '0' : case '1' : case '2' : case '3' : case '4' : case '5' : case '6' : case '7' :
+            std::sscanf(ns,"%o",&val); while (*ns>='0' && *ns<='7') ++ns;
+            *nd = val; break;
+          case 'x':
+            std::sscanf(++ns,"%x",&val); while ((*ns>='0' && *ns<='7') || (*ns>='a' && *ns<='f') || (*ns>='A' && *ns<='F')) ++ns;
+            *nd = val; break;
+          default : *nd = *(ns++);
+          } else *nd = *(ns++);
+    }
+
+    // Return a temporary string describing the size of a memory buffer.
+    inline const char *strbuffersize(const unsigned long size) {
+      static char res[256] = { 0 };
+      if (size<1024LU) cimg_snprintf(res,sizeof(res),"%lu byte%s",size,size>1?"s":"");
+      else if (size<1024*1024LU) { const float nsize = size/1024.0f; cimg_snprintf(res,sizeof(res),"%.1f Kb",nsize); }
+      else if (size<1024*1024*1024LU) { const float nsize = size/(1024*1024.0f); cimg_snprintf(res,sizeof(res),"%.1f Mb",nsize); }
+      else { const float nsize = size/(1024*1024*1024.0f); cimg_snprintf(res,sizeof(res),"%.1f Gb",nsize); }
+      return res;
+    }
+
+    //! Return the basename of a filename.
+    inline const char* basename(const char *const str)  {
+      const char *p = 0;
+      for (const char *np = str; np>=str && (p=np); np = std::strchr(np,cimg_file_separator)+1) {}
+      return p;
+    }
+
+    // Return a random filename.
+    inline const char* filenamerand() {
+      static char randomid[9] = { 0,0,0,0,0,0,0,0,0 };
+      cimg::srand();
+      for (unsigned int k = 0; k<8; ++k) {
+        const int v = (int)std::rand()%3;
+        randomid[k] = (char)(v==0?('0'+(std::rand()%10)):(v==1?('a'+(std::rand()%26)):('A'+(std::rand()%26))));
+      }
+      return randomid;
+    }
+
+    // Convert filename as a Windows-style filename (short path name).
+    inline void winformat_string(char *const str) {
+      if (str && *str) {
+#if cimg_OS==2
+        char *const nstr = new char[MAX_PATH];
+        if (GetShortPathNameA(str,nstr,MAX_PATH)) std::strcpy(str,nstr);
+#endif
+      }
+    }
+
+    //! Open a file.
+    /**
+       \param path Path of the filename to open.
+       \param mode C-string describing the opening mode.
+       \return Opened file.
+       \note Similar to <tt>std::fopen()</tt> but throw a \c CImgIOException when
+       the specified file cannot be opened, instead of returning \c 0.
+    **/
+    inline std::FILE *fopen(const char *const path, const char *const mode) {
+      if (!path)
+        throw CImgArgumentException("cimg::fopen() : Specified file path is (null).");
+      if (!mode)
+        throw CImgArgumentException("cimg::fopen() : File '%s', specified mode is (null).",
+                                    path);
+      std::FILE *res = 0;
+      if (*path=='-' && path[1]=='.') {
+        res = (*mode=='r')?stdin:stdout;
+#if cimg_OS==2
+        if (*mode && mode[1]=='b') { // Force stdin/stdout to be in binary mode.
+          if (_setmode(_fileno(res),0x8000)==-1) res = 0;
+        }
+#endif
+      } else res = std::fopen(path,mode);
+      if (!res) throw CImgIOException("cimg::fopen() : Failed to open file '%s' with mode '%s'.",
+                                      path,mode);
+      return res;
+    }
+
+    //! Close a file.
+    /**
+       \param file File to close.
+       \return \c 0 if file has been closed properly, something else otherwise.
+       \note Similar to <tt>std::fclose()</tt> but display a warning message if
+       the file has not been closed properly.
+    **/
+    inline int fclose(std::FILE *file) {
+      if (!file) warn("cimg::fclose() : Specified file is (null).");
+      if (!file || file==stdin || file==stdout) return 0;
+      const int errn = std::fclose(file);
+      if (errn!=0) warn("cimg::fclose() : Error code %d returned during file closing.",
+                        errn);
+      return errn;
+    }
+
+    //! Get/set path to store temporary files.
+    /**
+       \param user_path Specified path, or \c 0 to get the path currently used.
+       \param reinit_path Force path to be recalculated (may take some time).
+       \return Path where temporary files can be saved.
+    **/
+    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_snprintf(st_path,1024,"%s",p); \
+        cimg_snprintf(tmp,sizeof(tmp),"%s%c%s",st_path,cimg_file_separator,filetmp); \
+        if ((file=std::fopen(tmp,"wb"))!=0) { cimg::fclose(file); std::remove(tmp); path_found = true; } \
+      }
+      static char *st_path = 0;
+      if (reinit_path) { delete[] st_path; st_path = 0; }
+      if (user_path) {
+        if (!st_path) st_path = new char[1024];
+        std::memset(st_path,0,1024);
+        std::strncpy(st_path,user_path,1023);
+      } else if (!st_path) {
+        st_path = new char[1024];
+        std::memset(st_path,0,1024);
+        bool path_found = false;
+        char tmp[1024] = { 0 }, filetmp[512] = { 0 };
+        std::FILE *file = 0;
+        cimg_snprintf(filetmp,sizeof(filetmp),"%s.tmp",cimg::filenamerand());
+        char *tmpPath = std::getenv("TMP");
+        if (!tmpPath) { tmpPath = std::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;
+          std::strncpy(tmp,filetmp,sizeof(tmp)-1);
+          if ((file=std::fopen(tmp,"wb"))!=0) { cimg::fclose(file); std::remove(tmp); path_found = true; }
+        }
+        if (!path_found)
+          throw CImgIOException("cimg::temporary_path() : Failed to locate path for writing temporary files.\n");
+      }
+      return st_path;
+    }
+
+    //! Get/set path to the <i>Program Files/</i> directory (Windows only).
+    /**
+       \param user_path Specified path, or \c 0 to get the path currently used.
+       \param reinit_path Force path to be recalculated (may take some time).
+       \return Path containing the program files.
+    **/
+#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) { delete[] st_path; st_path = 0; }
+      if (user_path) {
+        if (!st_path) st_path = new char[1024];
+        std::memset(st_path,0,1024);
+        std::strncpy(st_path,user_path,1023);
+      } else if (!st_path) {
+        st_path = new char[MAX_PATH];
+        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 *const pfPath = std::getenv("PROGRAMFILES");
+          if (pfPath) std::strncpy(st_path,pfPath,MAX_PATH-1);
+          else std::strcpy(st_path,"C:\\PROGRA~1");
+        }
+#else
+        std::strcpy(st_path,"C:\\PROGRA~1");
+#endif
+      }
+      return st_path;
+    }
+#endif
+
+    //! Get/set path to the ImageMagick's \c convert binary.
+    /**
+       \param user_path Specified path, or \c 0 to get the path currently used.
+       \param reinit_path Force path to be recalculated (may take some time).
+       \return Path containing the \c convert binary.
+    **/
+    inline const char* imagemagick_path(const char *const user_path=0, const bool reinit_path=false) {
+      static char *st_path = 0;
+      if (reinit_path) { delete[] st_path; st_path = 0; }
+      if (user_path) {
+        if (!st_path) st_path = new char[1024];
+        std::memset(st_path,0,1024);
+        std::strncpy(st_path,user_path,1023);
+      } else if (!st_path) {
+        st_path = new char[1024];
+        std::memset(st_path,0,1024);
+        bool path_found = false;
+        std::FILE *file = 0;
+#if cimg_OS==2
+        const char *const pf_path = programfiles_path();
+        if (!path_found) {
+          std::strcpy(st_path,".\\convert.exe");
+          if ((file=std::fopen(st_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+        }
+        for (int k = 32; k>=10 && !path_found; --k) {
+          cimg_snprintf(st_path,sizeof(st_path),"%s\\IMAGEM~1.%.2d-\\convert.exe",pf_path,k);
+          if ((file=std::fopen(st_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+        }
+        for (int k = 9; k>=0 && !path_found; --k) {
+          cimg_snprintf(st_path,sizeof(st_path),"%s\\IMAGEM~1.%d-Q\\convert.exe",pf_path,k);
+          if ((file=std::fopen(st_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+        }
+        for (int k = 32; k>=0 && !path_found; --k) {
+          cimg_snprintf(st_path,sizeof(st_path),"%s\\IMAGEM~1.%d\\convert.exe",pf_path,k);
+          if ((file=std::fopen(st_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+        }
+        for (int k = 32; k>=10 && !path_found; --k) {
+          cimg_snprintf(st_path,sizeof(st_path),"%s\\IMAGEM~1.%.2d-\\VISUA~1\\BIN\\convert.exe",pf_path,k);
+          if ((file=std::fopen(st_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+        }
+        for (int k = 9; k>=0 && !path_found; --k) {
+          cimg_snprintf(st_path,sizeof(st_path),"%s\\IMAGEM~1.%d-Q\\VISUA~1\\BIN\\convert.exe",pf_path,k);
+          if ((file=std::fopen(st_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+        }
+        for (int k = 32; k>=0 && !path_found; --k) {
+          cimg_snprintf(st_path,sizeof(st_path),"%s\\IMAGEM~1.%d\\VISUA~1\\BIN\\convert.exe",pf_path,k);
+          if ((file=std::fopen(st_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+        }
+        for (int k = 32; k>=10 && !path_found; --k) {
+          cimg_snprintf(st_path,sizeof(st_path),"C:\\IMAGEM~1.%.2d-\\convert.exe",k);
+          if ((file=std::fopen(st_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+        }
+        for (int k = 9; k>=0 && !path_found; --k) {
+          cimg_snprintf(st_path,sizeof(st_path),"C:\\IMAGEM~1.%d-Q\\convert.exe",k);
+          if ((file=std::fopen(st_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+        }
+        for (int k = 32; k>=0 && !path_found; --k) {
+          cimg_snprintf(st_path,sizeof(st_path),"C:\\IMAGEM~1.%d\\convert.exe",k);
+          if ((file=std::fopen(st_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+        }
+        for (int k = 32; k>=10 && !path_found; --k) {
+          cimg_snprintf(st_path,sizeof(st_path),"C:\\IMAGEM~1.%.2d-\\VISUA~1\\BIN\\convert.exe",k);
+          if ((file=std::fopen(st_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+        }
+        for (int k = 9; k>=0 && !path_found; --k) {
+          cimg_snprintf(st_path,sizeof(st_path),"C:\\IMAGEM~1.%d-Q\\VISUA~1\\BIN\\convert.exe",k);
+          if ((file=std::fopen(st_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+        }
+        for (int k = 32; k>=0 && !path_found; --k) {
+          cimg_snprintf(st_path,sizeof(st_path),"C:\\IMAGEM~1.%d\\VISUA~1\\BIN\\convert.exe",k);
+          if ((file=std::fopen(st_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+        }
+        for (int k = 32; k>=10 && !path_found; --k) {
+          cimg_snprintf(st_path,sizeof(st_path),"D:\\IMAGEM~1.%.2d-\\convert.exe",k);
+          if ((file=std::fopen(st_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+        }
+        for (int k = 9; k>=0 && !path_found; --k) {
+          cimg_snprintf(st_path,sizeof(st_path),"D:\\IMAGEM~1.%d-Q\\convert.exe",k);
+          if ((file=std::fopen(st_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+        }
+        for (int k = 32; k>=0 && !path_found; --k) {
+          cimg_snprintf(st_path,sizeof(st_path),"D:\\IMAGEM~1.%d\\convert.exe",k);
+          if ((file=std::fopen(st_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+        }
+        for (int k = 32; k>=10 && !path_found; --k) {
+          cimg_snprintf(st_path,sizeof(st_path),"D:\\IMAGEM~1.%.2d-\\VISUA~1\\BIN\\convert.exe",k);
+          if ((file=std::fopen(st_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+        }
+        for (int k = 9; k>=0 && !path_found; --k) {
+          cimg_snprintf(st_path,sizeof(st_path),"D:\\IMAGEM~1.%d-Q\\VISUA~1\\BIN\\convert.exe",k);
+          if ((file=std::fopen(st_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+        }
+        for (int k = 32; k>=0 && !path_found; --k) {
+          cimg_snprintf(st_path,sizeof(st_path),"D:\\IMAGEM~1.%d\\VISUA~1\\BIN\\convert.exe",k);
+          if ((file=std::fopen(st_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+        }
+        if (!path_found) std::strcpy(st_path,"convert.exe");
+#else
+        if (!path_found) {
+          std::strcpy(st_path,"./convert");
+          if ((file=std::fopen(st_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+        }
+        if (!path_found) std::strcpy(st_path,"convert");
+#endif
+        winformat_string(st_path);
+      }
+      return st_path;
+    }
+
+    //! Get/set path to the GraphicsMagick's \c gm binary.
+    /**
+       \param user_path Specified path, or \c 0 to get the path currently used.
+       \param reinit_path Force path to be recalculated (may take some time).
+       \return Path containing the \c gm binary.
+    **/
+    inline const char* graphicsmagick_path(const char *const user_path=0, const bool reinit_path=false) {
+      static char *st_path = 0;
+      if (reinit_path) { delete[] st_path; st_path = 0; }
+      if (user_path) {
+        if (!st_path) st_path = new char[1024];
+        std::memset(st_path,0,1024);
+        std::strncpy(st_path,user_path,1023);
+      } else if (!st_path) {
+        st_path = new char[1024];
+        std::memset(st_path,0,1024);
+        bool path_found = false;
+        std::FILE *file = 0;
+#if cimg_OS==2
+        const char *const pf_path = programfiles_path();
+        if (!path_found) {
+          std::strcpy(st_path,".\\gm.exe");
+          if ((file=std::fopen(st_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+        }
+        for (int k = 32; k>=10 && !path_found; --k) {
+          cimg_snprintf(st_path,sizeof(st_path),"%s\\GRAPHI~1.%.2d-\\gm.exe",pf_path,k);
+          if ((file=std::fopen(st_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+        }
+        for (int k = 9; k>=0 && !path_found; --k) {
+          cimg_snprintf(st_path,sizeof(st_path),"%s\\GRAPHI~1.%d-Q\\gm.exe",pf_path,k);
+          if ((file=std::fopen(st_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+        }
+        for (int k = 32; k>=0 && !path_found; --k) {
+          cimg_snprintf(st_path,sizeof(st_path),"%s\\GRAPHI~1.%d\\gm.exe",pf_path,k);
+          if ((file=std::fopen(st_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+        }
+        for (int k = 32; k>=10 && !path_found; --k) {
+          cimg_snprintf(st_path,sizeof(st_path),"%s\\GRAPHI~1.%.2d-\\VISUA~1\\BIN\\gm.exe",pf_path,k);
+          if ((file=std::fopen(st_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+        }
+        for (int k = 9; k>=0 && !path_found; --k) {
+          cimg_snprintf(st_path,sizeof(st_path),"%s\\GRAPHI~1.%d-Q\\VISUA~1\\BIN\\gm.exe",pf_path,k);
+          if ((file=std::fopen(st_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+        }
+        for (int k = 32; k>=0 && !path_found; --k) {
+          cimg_snprintf(st_path,sizeof(st_path),"%s\\GRAPHI~1.%d\\VISUA~1\\BIN\\gm.exe",pf_path,k);
+          if ((file=std::fopen(st_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+        }
+        for (int k = 32; k>=10 && !path_found; --k) {
+          cimg_snprintf(st_path,sizeof(st_path),"C:\\GRAPHI~1.%.2d-\\gm.exe",k);
+          if ((file=std::fopen(st_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+        }
+        for (int k = 9; k>=0 && !path_found; --k) {
+          cimg_snprintf(st_path,sizeof(st_path),"C:\\GRAPHI~1.%d-Q\\gm.exe",k);
+          if ((file=std::fopen(st_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+        }
+        for (int k = 32; k>=0 && !path_found; --k) {
+          cimg_snprintf(st_path,sizeof(st_path),"C:\\GRAPHI~1.%d\\gm.exe",k);
+          if ((file=std::fopen(st_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+        }
+        for (int k = 32; k>=10 && !path_found; --k) {
+          cimg_snprintf(st_path,sizeof(st_path),"C:\\GRAPHI~1.%.2d-\\VISUA~1\\BIN\\gm.exe",k);
+          if ((file=std::fopen(st_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+        }
+        for (int k = 9; k>=0 && !path_found; --k) {
+          cimg_snprintf(st_path,sizeof(st_path),"C:\\GRAPHI~1.%d-Q\\VISUA~1\\BIN\\gm.exe",k);
+          if ((file=std::fopen(st_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+        }
+        for (int k = 32; k>=0 && !path_found; --k) {
+          cimg_snprintf(st_path,sizeof(st_path),"C:\\GRAPHI~1.%d\\VISUA~1\\BIN\\gm.exe",k);
+          if ((file=std::fopen(st_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+        }
+        for (int k = 32; k>=10 && !path_found; --k) {
+          cimg_snprintf(st_path,sizeof(st_path),"D:\\GRAPHI~1.%.2d-\\gm.exe",k);
+          if ((file=std::fopen(st_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+        }
+        for (int k = 9; k>=0 && !path_found; --k) {
+          cimg_snprintf(st_path,sizeof(st_path),"D:\\GRAPHI~1.%d-Q\\gm.exe",k);
+          if ((file=std::fopen(st_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+        }
+        for (int k = 32; k>=0 && !path_found; --k) {
+          cimg_snprintf(st_path,sizeof(st_path),"D:\\GRAPHI~1.%d\\gm.exe",k);
+          if ((file=std::fopen(st_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+        }
+        for (int k = 32; k>=10 && !path_found; --k) {
+          cimg_snprintf(st_path,sizeof(st_path),"D:\\GRAPHI~1.%.2d-\\VISUA~1\\BIN\\gm.exe",k);
+          if ((file=std::fopen(st_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+        }
+        for (int k = 9; k>=0 && !path_found; --k) {
+          cimg_snprintf(st_path,sizeof(st_path),"D:\\GRAPHI~1.%d-Q\\VISUA~1\\BIN\\gm.exe",k);
+          if ((file=std::fopen(st_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+        }
+        for (int k = 32; k>=0 && !path_found; --k) {
+          cimg_snprintf(st_path,sizeof(st_path),"D:\\GRAPHI~1.%d\\VISUA~1\\BIN\\gm.exe",k);
+          if ((file=std::fopen(st_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+        }
+        if (!path_found) std::strcpy(st_path,"gm.exe");
+#else
+        if (!path_found) {
+          std::strcpy(st_path,"./gm");
+          if ((file=std::fopen(st_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+        }
+        if (!path_found) std::strcpy(st_path,"gm");
+#endif
+        winformat_string(st_path);
+      }
+      return st_path;
+    }
+
+    //! Get/set path to the XMedcon's \c medcon binary.
+    /**
+       \param user_path Specified path, or \c 0 to get the path currently used.
+       \param reinit_path Force path to be recalculated (may take some time).
+       \return Path containing the \c medcon binary.
+    **/
+    inline const char* medcon_path(const char *const user_path=0, const bool reinit_path=false) {
+      static char *st_path = 0;
+      if (reinit_path) { delete[] st_path; st_path = 0; }
+      if (user_path) {
+        if (!st_path) st_path = new char[1024];
+        std::memset(st_path,0,1024);
+        std::strncpy(st_path,user_path,1023);
+      } else if (!st_path) {
+        st_path = new char[1024];
+        std::memset(st_path,0,1024);
+        bool path_found = false;
+        std::FILE *file = 0;
+#if cimg_OS==2
+        const char *const pf_path = programfiles_path();
+        if (!path_found) {
+          std::strcpy(st_path,".\\medcon.exe");
+          if ((file=std::fopen(st_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+        }
+        if (!path_found) {
+          cimg_snprintf(st_path,sizeof(st_path),"%s\\XMedCon\\bin\\medcon.bat",pf_path);
+          if ((file=std::fopen(st_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+        }
+        if (!path_found) {
+          cimg_snprintf(st_path,sizeof(st_path),"%s\\XMedCon\\bin\\medcon.exe",pf_path);
+          if ((file=std::fopen(st_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+        }
+        if (!path_found) std::strcpy(st_path,"medcon.exe");
+#else
+        if (!path_found) {
+          std::strcpy(st_path,"./medcon");
+          if ((file=std::fopen(st_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+        }
+        if (!path_found) std::strcpy(st_path,"medcon");
+#endif
+        winformat_string(st_path);
+      }
+      return st_path;
+    }
+
+    //! Get/set path to the FFMPEG's \c ffmpeg binary.
+    /**
+       \param user_path Specified path, or \c 0 to get the path currently used.
+       \param reinit_path Force path to be recalculated (may take some time).
+       \return Path containing the \c ffmpeg binary.
+    **/
+    inline const char *ffmpeg_path(const char *const user_path=0, const bool reinit_path=false) {
+      static char *st_path = 0;
+      if (reinit_path) { delete[] st_path; st_path = 0; }
+      if (user_path) {
+        if (!st_path) st_path = new char[1024];
+        std::memset(st_path,0,1024);
+        std::strncpy(st_path,user_path,1023);
+      } else if (!st_path) {
+        st_path = new char[1024];
+        std::memset(st_path,0,1024);
+        bool path_found = false;
+        std::FILE *file = 0;
+#if cimg_OS==2
+        if (!path_found) {
+          std::strcpy(st_path,".\\ffmpeg.exe");
+          if ((file=std::fopen(st_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+        }
+        if (!path_found) std::strcpy(st_path,"ffmpeg.exe");
+#else
+        if (!path_found) {
+          std::strcpy(st_path,"./ffmpeg");
+          if ((file=std::fopen(st_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+        }
+        if (!path_found) std::strcpy(st_path,"ffmpeg");
+#endif
+        winformat_string(st_path);
+      }
+      return st_path;
+    }
+
+    //! Get/set path to the \c gzip binary.
+    /**
+       \param user_path Specified path, or \c 0 to get the path currently used.
+       \param reinit_path Force path to be recalculated (may take some time).
+       \return Path containing the \c gzip binary.
+    **/
+    inline const char *gzip_path(const char *const user_path=0, const bool reinit_path=false) {
+      static char *st_path = 0;
+      if (reinit_path) { delete[] st_path; st_path = 0; }
+      if (user_path) {
+        if (!st_path) st_path = new char[1024];
+        std::memset(st_path,0,1024);
+        std::strncpy(st_path,user_path,1023);
+      } else if (!st_path) {
+        st_path = new char[1024];
+        std::memset(st_path,0,1024);
+        bool path_found = false;
+        std::FILE *file = 0;
+#if cimg_OS==2
+        if (!path_found) {
+          std::strcpy(st_path,".\\gzip.exe");
+          if ((file=std::fopen(st_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+        }
+        if (!path_found) std::strcpy(st_path,"gzip.exe");
+#else
+        if (!path_found) {
+          std::strcpy(st_path,"./gzip");
+          if ((file=std::fopen(st_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+        }
+        if (!path_found) std::strcpy(st_path,"gzip");
+#endif
+        winformat_string(st_path);
+      }
+      return st_path;
+    }
+
+    //! Get/set path to the \c gzip binary.
+    /**
+       \param user_path Specified path, or \c 0 to get the path currently used.
+       \param reinit_path Force path to be recalculated (may take some time).
+       \return Path containing the \c gunzip binary.
+    **/
+    inline const char *gunzip_path(const char *const user_path=0, const bool reinit_path=false) {
+      static char *st_path = 0;
+      if (reinit_path) { delete[] st_path; st_path = 0; }
+      if (user_path) {
+        if (!st_path) st_path = new char[1024];
+        std::memset(st_path,0,1024);
+        std::strncpy(st_path,user_path,1023);
+      } else if (!st_path) {
+        st_path = new char[1024];
+        std::memset(st_path,0,1024);
+        bool path_found = false;
+        std::FILE *file = 0;
+#if cimg_OS==2
+        if (!path_found) {
+          std::strcpy(st_path,".\\gunzip.exe");
+          if ((file=std::fopen(st_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+        }
+        if (!path_found) std::strcpy(st_path,"gunzip.exe");
+#else
+        if (!path_found) {
+          std::strcpy(st_path,"./gunzip");
+          if ((file=std::fopen(st_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+        }
+        if (!path_found) std::strcpy(st_path,"gunzip");
+#endif
+        winformat_string(st_path);
+      }
+      return st_path;
+    }
+
+    //! Get/set path to the \c dcraw binary.
+    /**
+       \param user_path Specified path, or \c 0 to get the path currently used.
+       \param reinit_path Force path to be recalculated (may take some time).
+       \return Path containing the \c dcraw binary.
+    **/
+    inline const char *dcraw_path(const char *const user_path=0, const bool reinit_path=false) {
+      static char *st_path = 0;
+      if (reinit_path) { delete[] st_path; st_path = 0; }
+      if (user_path) {
+        if (!st_path) st_path = new char[1024];
+        std::memset(st_path,0,1024);
+        std::strncpy(st_path,user_path,1023);
+      } else if (!st_path) {
+        st_path = new char[1024];
+        std::memset(st_path,0,1024);
+        bool path_found = false;
+        std::FILE *file = 0;
+#if cimg_OS==2
+        if (!path_found) {
+          std::strcpy(st_path,".\\dcraw.exe");
+          if ((file=std::fopen(st_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+        }
+        if (!path_found) std::strcpy(st_path,"dcraw.exe");
+#else
+        if (!path_found) {
+          std::strcpy(st_path,"./dcraw");
+          if ((file=std::fopen(st_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+        }
+        if (!path_found) std::strcpy(st_path,"dcraw");
+#endif
+        winformat_string(st_path);
+      }
+      return st_path;
+    }
+
+    //! Get/set path to the \c wget binary.
+    /**
+       \param user_path Specified path, or \c 0 to get the path currently used.
+       \param reinit_path Force path to be recalculated (may take some time).
+       \return Path containing the \c wget binary.
+    **/
+    inline const char *wget_path(const char *const user_path=0, const bool reinit_path=false) {
+      static char *st_path = 0;
+      if (reinit_path) { delete[] st_path; st_path = 0; }
+      if (user_path) {
+        if (!st_path) st_path = new char[1024];
+        std::memset(st_path,0,1024);
+        std::strncpy(st_path,user_path,1023);
+      } else if (!st_path) {
+        st_path = new char[1024];
+        std::memset(st_path,0,1024);
+        bool path_found = false;
+        std::FILE *file = 0;
+#if cimg_OS==2
+        if (!path_found) {
+          std::strcpy(st_path,".\\wget.exe");
+          if ((file=std::fopen(st_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+        }
+        if (!path_found) std::strcpy(st_path,"wget.exe");
+#else
+        if (!path_found) {
+          std::strcpy(st_path,"./wget");
+          if ((file=std::fopen(st_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+        }
+        if (!path_found) std::strcpy(st_path,"wget");
+#endif
+        winformat_string(st_path);
+      }
+      return st_path;
+    }
+
+    //! Get/set path to the \c curl binary.
+    /**
+       \param user_path Specified path, or \c 0 to get the path currently used.
+       \param reinit_path Force path to be recalculated (may take some time).
+       \return Path containing the \c curl binary.
+    **/
+    inline const char *curl_path(const char *const user_path=0, const bool reinit_path=false) {
+      static char *st_path = 0;
+      if (reinit_path) { delete[] st_path; st_path = 0; }
+      if (user_path) {
+        if (!st_path) st_path = new char[1024];
+        std::memset(st_path,0,1024);
+        std::strncpy(st_path,user_path,1023);
+      } else if (!st_path) {
+        st_path = new char[1024];
+        std::memset(st_path,0,1024);
+        bool path_found = false;
+        std::FILE *file = 0;
+#if cimg_OS==2
+        if (!path_found) {
+          std::strcpy(st_path,".\\curl.exe");
+          if ((file=std::fopen(st_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+        }
+        if (!path_found) std::strcpy(st_path,"curl.exe");
+#else
+        if (!path_found) {
+          std::strcpy(st_path,"./curl");
+          if ((file=std::fopen(st_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+        }
+        if (!path_found) std::strcpy(st_path,"curl");
+#endif
+        winformat_string(st_path);
+      }
+      return st_path;
+    }
+
+    //! Split filename into two C-strings \c body and \c extension.
+    inline const char *split_filename(const char *const filename, char *const body=0) {
+      if (!filename) { if (body) *body = 0; return 0; }
+      const char *p = 0; for (const char *np = filename; np>=filename && (p=np); np = std::strchr(np,'.')+1) {}
+      if (p==filename) {
+        if (body) std::strcpy(body,filename);
+        return filename + std::strlen(filename);
+      }
+      const unsigned int l = p - filename - 1;
+      if (body) { std::memcpy(body,filename,l); body[l] = 0; }
+      return p;
+    }
+
+    //! Create numbered version of a filename.
+    inline char* number_filename(const char *const filename, const int number, const unsigned int n, char *const str) {
+      if (!filename) { if (str) *str = 0; return 0; }
+      char format[1024] = { 0 }, body[1024] = { 0 };
+      const char *const ext = cimg::split_filename(filename,body);
+      if (n>0) cimg_snprintf(format,sizeof(format),"%s_%%.%ud.%s",body,n,ext);
+      else cimg_snprintf(format,sizeof(format),"%s_%%d.%s",body,ext);
+      std::sprintf(str,format,number);
+      return str;
+    }
+
+    //! Try to guess format from an image file.
+    /**
+       \param file Input file (can be \c 0 if \c filename is set).
+       \param filename Input filename (can be \c 0 if \c file is set).
+       \return C-string containing the guessed file format, or \c 0 if nothing has been guessed.
+     **/
+    inline const char *file_type(std::FILE *const file, const char *const filename) {
+      if (!file && !filename)
+        throw CImgArgumentException("cimg::file_type() : Specified filename is (null).");
+      static const char
+        *const _pnm = "pnm",
+        *const _pfm = "pfm",
+        *const _bmp = "bmp",
+        *const _gif = "gif",
+        *const _jpg = "jpg",
+        *const _off = "off",
+        *const _pan = "pan",
+        *const _png = "png",
+        *const _tif = "tif",
+        *const _inr = "inr",
+        *const _dcm = "dcm";
+      std::FILE *const nfile = file?file:cimg::fopen(filename,"rb");
+      const char *f_type = 0, *head;
+      char header[2048] = { 0 }, item[1024] = { 0 };
+      const unsigned char *const uheader = (unsigned char*)header;
+      int err; char cerr;
+      const unsigned int siz = (unsigned int)std::fread(header,2048,1,nfile);   // Read first 2048 bytes.
+      if (!file) cimg::fclose(nfile);
+
+      if (!std::strncmp(header,"OFF\n",4)) f_type = _off; // Check for OFF format.
+      else if (!std::strncmp(header,"#INRIMAGE",9)) f_type = _inr; // Check for INRIMAGE format.
+      else if (!std::strncmp(header,"PANDORE",7)) f_type = _pan; // Check for PANDORE format.
+      else if (!std::strncmp(header+128,"DICM",4)) f_type = _dcm; // Check for DICOM format.
+      else if (uheader[0]==0xFF && uheader[1]==0xD8 && uheader[2]==0xFF) f_type = _jpg;  // Check for JPEG format.
+      else if (header[0]=='B' && header[1]=='M') f_type = _bmp;  // Check for BMP format.
+      else if (header[0]=='G' && header[1]=='I' && header[2]=='F' && header[3]=='8' && header[5]=='a' && // Check for GIF format.
+               (header[4]=='7' || header[4]=='9')) f_type = _gif;
+      else if (uheader[0]==0x89 && uheader[1]==0x50 && uheader[2]==0x4E && uheader[3]==0x47 &&  // Check for PNG format.
+               uheader[4]==0x0D && uheader[5]==0x0A && uheader[6]==0x1A && uheader[7]==0x0A) f_type = _png;
+      else if ((uheader[0]==0x49 && uheader[1]==0x49) || (uheader[0]==0x4D && uheader[1]==0x4D)) f_type = _tif; // Check for TIFF format.
+      else { // Check for PNM or PFM format.
+        head = header;
+        while (head<header+siz && (err=std::sscanf(head,"%1023[^\n]",item))!=EOF && (*item=='#' || !err))
+          head+=1+(err?std::strlen(item):0);
+        if (std::sscanf(item," P%d",&err)==1) f_type = _pnm;
+        else if (std::sscanf(item," P%c",&cerr)==1 && (cerr=='f' || cerr=='F')) f_type = _pfm;
+      }
+      return f_type;
+    }
+
+    //! Read data from file.
+    /**
+       \param[out] ptr Pointer to memory buffer that will contain the binary data read from file.
+       \param nmemb Number of elements to read.
+       \param stream File to read data from.
+       \return Number of read elements.
+       \note Similar to <tt>std::fread()</tt> but may display warning message if all elements could not be read.
+    **/
+    template<typename T>
+    inline int fread(T *const ptr, const unsigned int nmemb, std::FILE *stream) {
+      if (!ptr || nmemb<=0 || !stream)
+        throw CImgArgumentException("cimg::fread() : Invalid reading request of %u %s%s from file %p to buffer %p.",
+                                    nmemb,cimg::type<T>::string(),nmemb>1?"s":"",stream,ptr);
+
+      const unsigned long wlimitT = 63*1024*1024, wlimit = wlimitT/sizeof(T);
+      unsigned int to_read = nmemb, al_read = 0, l_to_read = 0, l_al_read = 0;
+      do {
+        l_to_read = (to_read*sizeof(T))<wlimitT?to_read:wlimit;
+        l_al_read = (unsigned int)std::fread((void*)(ptr+al_read),sizeof(T),l_to_read,stream);
+        al_read+=l_al_read;
+        to_read-=l_al_read;
+      } while (l_to_read==l_al_read && to_read>0);
+      if (to_read>0)
+        warn("cimg::fread() : Only %u/%u elements could be read from file.",
+             al_read,nmemb);
+      return al_read;
+    }
+
+    //! Write data to file.
+    /**
+       \param ptr Pointer to memory buffer containing the binary data to write on file.
+       \param nmemb Number of elements to write.
+       \param[out] stream File to write data on.
+       \return Number of written elements.
+       \note Similar to <tt>std::fwrite</tt> but may display warning messages if all elements could not be written.
+    **/
+    template<typename T>
+    inline int fwrite(const T *ptr, const unsigned int nmemb, std::FILE *stream) {
+      if (!ptr || !stream)
+        throw CImgArgumentException("cimg::fwrite() : Invalid writing request of %u %s%s from buffer %p to file %p.",
+                                    nmemb,cimg::type<T>::string(),nmemb>1?"s":"",ptr,stream);
+      if (nmemb<=0) return 0;
+      const unsigned long wlimitT = 63*1024*1024, wlimit = wlimitT/sizeof(T);
+      unsigned int to_write = nmemb, al_write = 0, l_to_write = 0, l_al_write = 0;
+      do {
+        l_to_write = (to_write*sizeof(T))<wlimitT?to_write:wlimit;
+        l_al_write = (unsigned int)std::fwrite((void*)(ptr+al_write),sizeof(T),l_to_write,stream);
+        al_write+=l_al_write;
+        to_write-=l_al_write;
+      } while (l_to_write==l_al_write && to_write>0);
+      if (to_write>0)
+        warn("cimg::fwrite() : Only %u/%u elements could be written in file.",
+             al_write,nmemb);
+      return al_write;
+    }
+
+    //! Load file from network as a local temporary file.
+    /**
+       \param filename Path to the filename to read from network.
+       \param[out] filename_local C-string containing the path to a local copy of \c filename.
+       \return Value of \c filename_local.
+       \note Use external binaries \c wget or \c curl to perform. You must have one of these tools installed
+       to be able to use this function.
+    **/
+    inline char *load_network_external(const char *const filename, char *const filename_local) {
+      if (!filename)
+        throw CImgArgumentException("cimg::load_network_external() : Specified filename is (null).");
+      if (!filename_local)
+        throw CImgArgumentException("cimg::load_network_external() : Specified destination string is (null).");
+      const char *const _ext = cimg::split_filename(filename), *const ext = (*_ext && _ext>filename)?_ext-1:_ext;
+      char command[1024] = { 0 };
+      std::FILE *file = 0;
+      *filename_local = 0;
+      do {
+        cimg_snprintf(filename_local,512,"%s%c%s%s",cimg::temporary_path(),cimg_file_separator,cimg::filenamerand(),ext);
+        if ((file=std::fopen(filename_local,"rb"))!=0) cimg::fclose(file);
+      } while (file);
+
+      // Try with 'curl' first.
+      cimg_snprintf(command,sizeof(command),"%s -f --silent --compressed -o \"%s\" \"%s\"",cimg::curl_path(),filename_local,filename);
+      cimg::system(command);
+      if (!(file = std::fopen(filename_local,"rb"))) {
+
+        // Try with 'wget' else.
+        cimg_snprintf(command,sizeof(command),"%s -q -r -l 0 --no-cache -O \"%s\" \"%s\"",cimg::wget_path(),filename_local,filename);
+        cimg::system(command);
+        if (!(file = std::fopen(filename_local,"rb")))
+          throw CImgIOException("cimg::load_network_external() : Failed to load file '%s' with external tools 'wget' or 'curl'.",filename);
+        cimg::fclose(file);
+
+        // Try gunzip it.
+        cimg_snprintf(command,sizeof(command),"%s.gz",filename_local);
+        std::rename(filename_local,command);
+        cimg_snprintf(command,sizeof(command),"%s --quiet %s.gz",gunzip_path(),filename_local);
+        cimg::system(command);
+        file = std::fopen(filename_local,"rb");
+        if (!file) {
+          cimg_snprintf(command,sizeof(command),"%s.gz",filename_local);
+          std::rename(command,filename_local);
+          file = std::fopen(filename_local,"rb");
+        }
+      }
+      std::fseek(file,0,SEEK_END); // Check if file size is 0.
+      if (!(unsigned int)std::ftell(file))
+        throw CImgIOException("cimg::load_network_external() : Failed to load file '%s' with external commands 'wget' or 'curl'.",filename);
+      cimg::fclose(file);
+      return filename_local;
+    }
+
+    // Return options specified on the command line (internal).
+    inline const char* option(const char *const name, const int argc, const char *const *const argv,
+                              const char *const defaut, const char *const usage, const bool reset_static) {
+      static bool first = true, visu = false;
+      if (reset_static) { first = true; return 0; }
+      const char *res = 0;
+      if (first) {
+        first = false;
+        visu = cimg::option("-h",argc,argv,(char*)0,(char*)0,false)!=0;
+        visu |= cimg::option("-help",argc,argv,(char*)0,(char*)0,false)!=0;
+        visu |= cimg::option("--help",argc,argv,(char*)0,(char*)0,false)!=0;
+      }
+      if (!name && visu) {
+        if (usage) {
+          std::fprintf(cimg::output(),"\n %s%s%s",cimg::t_red,cimg::basename(argv[0]),cimg::t_normal);
+          std::fprintf(cimg::output()," : %s",usage);
+          std::fprintf(cimg::output()," (%s, %s)\n\n",__DATE__,__TIME__);
+        }
+        if (defaut) std::fprintf(cimg::output(),"%s\n",defaut);
+      }
+      if (name) {
+        if (argc>0) {
+          int k = 0;
+          while (k<argc && std::strcmp(argv[k],name)) ++k;
+          res = (k++==argc?defaut:(k==argc?argv[--k]:argv[k]));
+        } else res = defaut;
+        if (visu && usage) std::fprintf(cimg::output(),"    %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 const char* option(const char *const name, const int argc, const char *const *const argv,
+                              const char *const defaut, const char *const usage=0) {
+      return option(name,argc,argv,defaut,usage,false);
+    }
+
+    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 *const 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 *const s = cimg::option(name,argc,argv,(char*)0);
+      const int res = s?std::atoi(s):defaut;
+      char tmp[256] = { 0 };
+      cimg_snprintf(tmp,sizeof(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 *const s = cimg::option(name,argc,argv,(char*)0);
+      const char res = s?*s:defaut;
+      char tmp[8] = { 0 };
+      *tmp = res;
+      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 *const s = cimg::option(name,argc,argv,(char*)0);
+      const float res = s?(float)cimg::atof(s):defaut;
+      char tmp[256] = { 0 };
+      cimg_snprintf(tmp,sizeof(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 *const s = cimg::option(name,argc,argv,(char*)0);
+      const double res = s?cimg::atof(s):defaut;
+      char tmp[256] = { 0 };
+      cimg_snprintf(tmp,sizeof(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.
+    /**
+       \note Output is done on the default output stream.
+       \see output().
+    **/
+    inline void info() {
+      char tmp[1024] = { 0 };
+      std::fprintf(cimg::output(),"\n %s%sCImg Library %u.%u.%u%s, compiled %s ( %s ) with the following flags :\n\n",
+                   cimg::t_red,cimg::t_bold,cimg_version/100,(cimg_version/10)%10,cimg_version%10,
+                   cimg::t_normal,__DATE__,__TIME__);
+
+      std::fprintf(cimg::output(),"  > 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);
+
+      std::fprintf(cimg::output(),"  > CPU endianness :         %s%s Endian%s\n",
+                   cimg::t_bold,
+                   cimg::endianness()?"Big":"Little",
+                   cimg::t_normal);
+
+      std::fprintf(cimg::output(),"  > Verbosity mode :         %s%-13s%s %s('cimg_verbosity'=%d)%s\n",
+                   cimg::t_bold,
+                   cimg_verbosity==0?"Quiet":(cimg_verbosity==1?"Console":(cimg_verbosity==2?"Dialog":(cimg_verbosity==3?"Console+Warnings":"Dialog+Warnings"))),
+                   cimg::t_normal,cimg::t_green,
+                   cimg_verbosity,
+                   cimg::t_normal);
+
+      std::fprintf(cimg::output(),"  > 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);
+
+      std::fprintf(cimg::output(),"  > 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);
+
+      std::fprintf(cimg::output(),"  > 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":"Unknown",
+                   cimg::t_normal,cimg::t_green,
+                   cimg_display,
+                   cimg::t_normal);
+
+#if cimg_display==1
+      std::fprintf(cimg::output(),"  > 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);
+
+      std::fprintf(cimg::output(),"  > 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
+      std::fprintf(cimg::output(),"  > 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);
+      std::fprintf(cimg::output(),"  > 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);
+      std::fprintf(cimg::output(),"  > 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);
+
+      std::fprintf(cimg::output(),"  > 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);
+
+      std::fprintf(cimg::output(),"  > 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);
+
+      std::fprintf(cimg::output(),"  > 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);
+
+      std::fprintf(cimg::output(),"  > 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_snprintf(tmp,sizeof(tmp),"\"%.1020s\"",cimg::imagemagick_path());
+      std::fprintf(cimg::output(),"  > Path of ImageMagick :    %s%-13s%s\n",
+                   cimg::t_bold,
+                   tmp,
+                   cimg::t_normal);
+
+      cimg_snprintf(tmp,sizeof(tmp),"\"%.1020s\"",cimg::graphicsmagick_path());
+      std::fprintf(cimg::output(),"  > Path of GraphicsMagick : %s%-13s%s\n",
+                   cimg::t_bold,
+                   tmp,
+                   cimg::t_normal);
+
+      cimg_snprintf(tmp,sizeof(tmp),"\"%.1020s\"",cimg::medcon_path());
+      std::fprintf(cimg::output(),"  > Path of 'medcon' :       %s%-13s%s\n",
+                   cimg::t_bold,
+                   tmp,
+                   cimg::t_normal);
+
+      cimg_snprintf(tmp,sizeof(tmp),"\"%.1020s\"",cimg::temporary_path());
+      std::fprintf(cimg::output(),"  > Temporary path :         %s%-13s%s\n",
+                   cimg::t_bold,
+                   tmp,
+                   cimg::t_normal);
+
+      std::fprintf(cimg::output(),"\n");
+    }
+
+    // Declare LAPACK function signatures if LAPACK support is enabled.
+#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.
+   #
+   #
+   -------------------------------------------------*/
+
+#define _cimg_create_ext_operators(typ) \
+  template<typename T> \
+  inline CImg<typename cimg::superset<T,typ>::type> operator+(const typ val, const CImg<T>& img) { \
+    return img + val; \
+  } \
+  template<typename T> \
+  inline CImg<typename cimg::superset<T,typ>::type> operator-(const typ val, const CImg<T>& img) { \
+    typedef typename cimg::superset<T,typ>::type Tt; \
+    return CImg<Tt>(img._width,img._height,img._depth,img._spectrum,val)-=img; \
+  } \
+  template<typename T> \
+  inline CImg<typename cimg::superset<T,typ>::type> operator*(const typ val, const CImg<T>& img) { \
+    return img*val; \
+  } \
+  template<typename T> \
+  inline CImg<typename cimg::superset<T,typ>::type> operator/(const typ val, const CImg<T>& img) { \
+    return val*img.get_invert(); \
+  } \
+  template<typename T> \
+  inline CImg<typename cimg::superset<T,typ>::type> operator&(const typ val, const CImg<T>& img) { \
+    return img & val; \
+  } \
+  template<typename T> \
+  inline CImg<typename cimg::superset<T,typ>::type> operator|(const typ val, const CImg<T>& img) { \
+    return img | val; \
+  } \
+  template<typename T> \
+  inline CImg<typename cimg::superset<T,typ>::type> operator^(const typ val, const CImg<T>& img) { \
+    return img ^ val; \
+  } \
+  template<typename T> \
+  inline bool operator==(const typ val, const CImg<T>& img) {   \
+    return img == val; \
+  } \
+  template<typename T> \
+  inline bool operator!=(const typ val, const CImg<T>& img) { \
+    return img != val; \
+  }
+
+  _cimg_create_ext_operators(bool)
+  _cimg_create_ext_operators(unsigned char)
+  _cimg_create_ext_operators(char)
+  _cimg_create_ext_operators(signed char)
+  _cimg_create_ext_operators(unsigned short)
+  _cimg_create_ext_operators(short)
+  _cimg_create_ext_operators(unsigned int)
+  _cimg_create_ext_operators(int)
+  _cimg_create_ext_operators(unsigned long)
+  _cimg_create_ext_operators(long)
+  _cimg_create_ext_operators(float)
+  _cimg_create_ext_operators(double)
+
+  template<typename T>
+  inline CImg<_cimg_Tfloat> operator+(const char *const expression, const CImg<T>& img) {
+    return img + expression;
+  }
+
+  template<typename T>
+  inline CImg<_cimg_Tfloat> operator-(const char *const expression, const CImg<T>& img) {
+    return CImg<_cimg_Tfloat>(img._width,img._height,img._depth,img._spectrum,expression,true)-=img;
+  }
+
+  template<typename T>
+  inline CImg<_cimg_Tfloat> operator*(const char *const expression, const CImg<T>& img) {
+    return img*expression;
+  }
+
+  template<typename T>
+  inline CImg<_cimg_Tfloat> operator/(const char *const expression, const CImg<T>& img) {
+    return expression*img.get_invert();
+  }
+
+  template<typename T>
+  inline CImg<T> operator&(const char *const expression, const CImg<T>& img) {
+    return img & expression;
+  }
+
+  template<typename T>
+  inline CImg<T> operator|(const char *const expression, const CImg<T>& img) {
+    return img | expression;
+  }
+
+  template<typename T>
+  inline CImg<T> operator^(const char *const expression, const CImg<T>& img) {
+    return img ^ expression;
+  }
+
+  template<typename T>
+  inline bool operator==(const char *const expression, const CImg<T>& img) {
+    return img == expression;
+  }
+
+  template<typename T>
+  inline bool operator!=(const char *const expression, const CImg<T>& img) {
+    return img != expression;
+  }
+
+  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> log2(const CImg<T>& instance) {
+    return instance.get_log2();
+  }
+
+  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> sign(const CImg<T>& instance) {
+    return instance.get_sign();
+  }
+
+  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> sinc(const CImg<T>& instance) {
+    return instance.get_sinc();
+  }
+
+  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<_cimg_Tfloat> cosh(const CImg<T>& instance) {
+    return instance.get_cosh();
+  }
+
+  template<typename T>
+  inline CImg<_cimg_Tfloat> sinh(const CImg<T>& instance) {
+    return instance.get_sinh();
+  }
+
+  template<typename T>
+  inline CImg<_cimg_Tfloat> tanh(const CImg<T>& instance) {
+    return instance.get_tanh();
+  }
+
+  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();
+  }
+
+  /*-----------------------------------
+   #
+   # Define the CImgDisplay structure
+   #
+   ----------------------------------*/
+  //! Allow to create windows, display images on them and manage user events (keyboard, mouse and windows events).
+  /**
+     CImgDisplay methods rely on a low-level graphic library to perform : it can be either \b X-Window (X11, for Unix-based systems)
+     or \b GDI32 (for Windows-based systems).
+     If both libraries are missing, CImgDisplay will not be able to display images on screen, and will enter a minimal mode
+     where warning messages will be outputed each time the program is trying to call one of the CImgDisplay method.
+
+     The configuration variable \c cimg_display tells about the graphic library used.
+     It is set automatically by \CImg when one of these graphic libraries has been detected.
+     But, you can override its value if necessary. Valid choices are :
+     - 0 : Disable display capabilities.
+     - 1 : Use \b X-Window (X11) library.
+     - 2 : Use \b GDI32 library.
+
+     Remember to link your program against \b X11 or \b GDI32 libraries if you use CImgDisplay.
+  **/
+  struct CImgDisplay {
+    unsigned long _timer, _fps_frames, _fps_timer;
+    unsigned int _width, _height, _normalization;
+    float _fps_fps, _min, _max;
+    bool _is_fullscreen;
+    char *_title;
+    volatile unsigned int _window_width, _window_height, _button, _keys[128], _released_keys[128];
+    volatile int _window_x, _window_y, _mouse_x, _mouse_y, _wheel;
+    volatile bool _is_closed, _is_resized, _is_moved, _is_event,
+      _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;
+
+    //@}
+    //---------------------------
+    //
+    //! \name Plugins
+    //@{
+    //---------------------------
+
+#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
+
+    //@}
+    //--------------------------------------------------------
+    //
+    //! \name Constructors / Destructor / Instance Management
+    //@{
+    //--------------------------------------------------------
+
+    //! Destructor.
+    /**
+       \note If the associated window is visible on the screen, it is closed by the call to the destructor.
+       \see CImgDisplay(), assign().
+    **/
+    ~CImgDisplay() {
+      assign();
+    }
+
+    //! Create an empty display.
+    /**
+       \note Constructing an empty CImgDisplay instance does not make a window appearing on the screen, until
+       display of valid data is performed.
+       \par Example
+       \code
+       CImgDisplay disp;  // Does actually nothing.
+       ...
+       disp.display(img); // Create new window and display image in it.
+       \endcode
+       \see ~CImgDisplay(), assign().
+    **/
+    CImgDisplay():
+      _width(0),_height(0),_normalization(0),
+      _min(0),_max(0),
+      _is_fullscreen(false),
+      _title(0),
+      _window_width(0),_window_height(0),_button(0),
+      _window_x(0),_window_y(0),_mouse_x(-1),_mouse_y(-1),_wheel(0),
+      _is_closed(true),_is_resized(false),_is_moved(false),_is_event(false) {
+      assign();
+    }
+
+    //! Create a display with specified dimensions.
+    /** \param width Window width.
+        \param height Window height.
+        \param title Window title.
+        \param normalization Normalization type (<tt>0</tt>=none, <tt>1</tt>=always, <tt>2</tt>=once, <tt>3</tt>=pixel type-dependent, see normalization()).
+        \param is_fullscreen Flag telling if fullscreen mode is enabled.
+        \param is_closed Flag telling if associated window is initially visible or not.
+        \note A black background is initially displayed on the associated window.
+        \see assign(unsigned int,unsigned int,const char*,unsigned int,bool,bool).
+    **/
+    CImgDisplay(const unsigned int width, const unsigned int height,
+                const char *const title=0, const unsigned int normalization=3,
+                const bool is_fullscreen=false, const bool is_closed=false):
+      _width(0),_height(0),_normalization(0),
+      _min(0),_max(0),
+      _is_fullscreen(false),
+      _title(0),
+      _window_width(0),_window_height(0),_button(0),
+      _window_x(0),_window_y(0),_mouse_x(-1),_mouse_y(-1),_wheel(0),
+      _is_closed(true),_is_resized(false),_is_moved(false),_is_event(false) {
+      assign(width,height,title,normalization,is_fullscreen,is_closed);
+    }
+
+    //! Create a display from an image.
+    /** \param img Image used as a model to create the window.
+        \param title Window title.
+        \param normalization Normalization type (<tt>0</tt>=none, <tt>1</tt>=always, <tt>2</tt>=once, <tt>3</tt>=pixel type-dependent, see normalization()).
+        \param is_fullscreen Flag telling if fullscreen mode is enabled.
+        \param is_closed Flag telling if associated window is initially visible or not.
+        \note The pixels of the input image are initially displayed on the associated window.
+        \see assign(const CImg<T>&,const char*,unsigned int,bool,bool).
+    **/
+    template<typename T>
+    explicit CImgDisplay(const CImg<T>& img,
+                         const char *const title=0, const unsigned int normalization=3,
+                         const bool is_fullscreen=false, const bool is_closed=false):
+      _width(0),_height(0),_normalization(0),
+      _min(0),_max(0),
+      _is_fullscreen(false),
+      _title(0),
+      _window_width(0),_window_height(0),_button(0),
+      _window_x(0),_window_y(0),_mouse_x(-1),_mouse_y(-1),_wheel(0),
+      _is_closed(true),_is_resized(false),_is_moved(false),_is_event(false) {
+      assign(img,title,normalization,is_fullscreen,is_closed);
+    }
+
+    //! Create a display from an image list.
+    /** \param list The images list to display.
+        \param title Window title.
+        \param normalization Normalization type (<tt>0</tt>=none, <tt>1</tt>=always, <tt>2</tt>=once, <tt>3</tt>=pixel type-dependent, see normalization()).
+        \param is_fullscreen Flag telling if fullscreen mode is enabled.
+        \param is_closed Flag telling if associated window is initially visible or not.
+        \note All images of the list, concatenated along the X-axis, are initially displayed on the associated window.
+        \see assign(const CImgList<T>&,const char*,unsigned int,bool,bool).
+    **/
+    template<typename T>
+    explicit CImgDisplay(const CImgList<T>& list,
+                         const char *const title=0, const unsigned int normalization=3,
+                         const bool is_fullscreen=false, const bool is_closed=false):
+      _width(0),_height(0),_normalization(0),
+      _min(0),_max(0),
+      _is_fullscreen(false),
+      _title(0),
+      _window_width(0),_window_height(0),_button(0),
+      _window_x(0),_window_y(0),_mouse_x(-1),_mouse_y(-1),_wheel(0),
+      _is_closed(true),_is_resized(false),_is_moved(false),_is_event(false) {
+      assign(list,title,normalization,is_fullscreen,is_closed);
+    }
+
+    //! Create a display as a copy of an existing one.
+    /**
+        \param disp  : Display instance to copy.
+        \note The pixel buffer of the input window is initially displayed on the associated window.
+        \see assign(const CImgDisplay&).
+    **/
+    CImgDisplay(const CImgDisplay& disp):
+      _width(0),_height(0),_normalization(0),
+      _min(0),_max(0),
+      _is_fullscreen(false),
+      _title(0),
+      _window_width(0),_window_height(0),_button(0),
+      _window_x(0),_window_y(0),_mouse_x(-1),_mouse_y(-1),_wheel(0),
+      _is_closed(true),_is_resized(false),_is_moved(false),_is_event(false) {
+      assign(disp);
+    }
+
+#if cimg_display==0
+
+    static void _no_display_exception() {
+      throw CImgDisplayException("CImgDisplay() : No display available.");
+    }
+
+    //! Destructor - Empty constructor \inplace.
+    /**
+       \note Replace the current instance by an empty display.
+       \see ~CImgDisplay(), CImgDisplay().
+    **/
+    CImgDisplay& assign() {
+      return flush();
+    }
+
+    //! Create a display with specified dimensions \inplace.
+    /**
+       \see CImgDisplay(unsigned int,unsigned int,const char*,unsigned int,bool,bool).
+    **/
+    CImgDisplay& assign(const unsigned int width, const unsigned int height,
+                        const char *const title=0, const unsigned int normalization=3,
+                        const bool is_fullscreen=false, const bool is_closed=false) {
+      cimg::unused(width,height,title,normalization,is_fullscreen,is_closed);
+      _no_display_exception();
+      return assign();
+    }
+
+    //! Create a display from an image \inplace.
+    /**
+       \see CImgDisplay(const CImg<T>&,const char*,unsigned int,bool,bool).
+    **/
+    template<typename T>
+    CImgDisplay& assign(const CImg<T>& img,
+                        const char *const title=0, const unsigned int normalization=3,
+                        const bool is_fullscreen=false, const bool is_closed=false) {
+      _no_display_exception();
+      return assign(img._width,img._height,title,normalization,is_fullscreen,is_closed);
+    }
+
+    //! Create a display from an image list \inplace.
+    /**
+       \see CImgDisplay(const CImgList<T>&,const char*,unsigned int,bool,bool).
+    **/
+    template<typename T>
+    CImgDisplay& assign(const CImgList<T>& list,
+                        const char *const title=0, const unsigned int normalization=3,
+                        const bool is_fullscreen=false, const bool is_closed=false) {
+      _no_display_exception();
+      return assign(list._width,list._width,title,normalization,is_fullscreen,is_closed);
+    }
+
+    //! Create a display as a copy of another one \inplace.
+    /**
+       \see CImgDisplay(const CImgDisplay&).
+    **/
+    CImgDisplay& assign(const CImgDisplay &disp) {
+      _no_display_exception();
+      return assign(disp._width,disp._height);
+    }
+
+#endif
+
+    //! Return a reference to an empty display.
+    /**
+       \note Can be useful for writing function prototypes where one of the argument (of type CImgDisplay&)
+       must have a default value.
+       \par Example
+       \code
+       void foo(CImgDisplay& disp=CImgDisplay::empty());
+       \endcode
+    **/
+    static CImgDisplay& empty() {
+      static CImgDisplay _empty;
+      return _empty.assign();
+    }
+
+#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, const unsigned int dz,
+                                   const int dmin, const int dmax,const bool return_y) {
+      unsigned int nw = dx + (dz>1?dz:0), nh = dy + (dz>1?dz:0);
+      const unsigned int
+        sw = CImgDisplay::screen_width(), sh = CImgDisplay::screen_height(),
+        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?1:0); nw = mw; }
+      if (nh<mh) { nw = nw*mh/nh; nw+=(nw==0?1:0); nh = mh; }
+      if (nw>Mw) { nh = nh*Mw/nw; nh+=(nh==0?1:0); nw = Mw; }
+      if (nh>Mh) { nw = nw*Mh/nh; nw+=(nw==0?1:0); nh = Mh; }
+      if (nw<mw) nw = mw;
+      if (nh<mh) nh = mh;
+      return return_y?nh:nw;
+    }
+
+    //@}
+    //------------------------------------------
+    //
+    //! \name Overloaded Operators
+    //@{
+    //------------------------------------------
+
+    //! Display image on associated window.
+    /**
+       \note <tt>disp = img</tt> is equivalent to <tt>disp.display(img)</tt>.
+       \see display(const CImg<T>&).
+    **/
+    template<typename t>
+    CImgDisplay& operator=(const CImg<t>& img) {
+      return display(img);
+    }
+
+    //! Display list of images on associated window.
+    /**
+       \note <tt>disp = list</tt> is equivalent to <tt>disp.display(list)</tt>.
+       \see display(const CImgList<T>&,char,float).
+    **/
+    template<typename t>
+    CImgDisplay& operator=(const CImgList<t>& list) {
+      return display(list);
+    }
+
+    //! Create a display as a copy of another one \inplace.
+    /**
+       \note Equivalent to assign(const CImgDisplay&).
+       \see assign(const CImgDisplay&).
+     **/
+    CImgDisplay& operator=(const CImgDisplay& disp) {
+      return assign(disp);
+    }
+
+    //! Return \c false if display is empty, \c true otherwise.
+    /**
+       \note <tt>if (disp) { ... }</tt> is equivalent to <tt>if (!disp.is_empty()) { ... }</tt>.
+       \see is_empty().
+    **/
+    operator bool() const {
+      return !is_empty();
+    }
+
+    //@}
+    //------------------------------------------
+    //
+    //! \name Instance Checking
+    //@{
+    //------------------------------------------
+
+    //! Return \c true if display is empty, \c false otherwise.
+    /**
+       \see operator bool().
+    **/
+    bool is_empty() const {
+      return !(_width && _height);
+    }
+
+    //! Return \c true if display is closed (i.e. not visible on the screen), \c false otherwise.
+    /**
+       \note
+       - When a user physically closes the associated window, the display is set to closed.
+       - A closed display is not destroyed. Its associated window can be show again on the screen using show().
+       \see is_event(), show(), close().
+    **/
+    bool is_closed() const {
+      return _is_closed;
+    }
+
+    //! Return \c true if associated window has been resized on the screen, \c false otherwise.
+    /**
+       \see is_event(), resize().
+    **/
+    bool is_resized() const {
+      return _is_resized;
+    }
+
+    //! Return \c true if associated window has been moved on the screen, \c false otherwise.
+    /**
+       \see is_event(), move().
+    **/
+    bool is_moved() const {
+      return _is_moved;
+    }
+
+    //! Return \c true if any event has occured on the associated window, \c false otherwise.
+    /**
+       \see is_closed(), is_resize(), is_moved(), is_key().
+    **/
+    bool is_event() const {
+      return _is_event;
+    }
+
+    //! Return \c true if current display is in fullscreen mode, \c false otherwise.
+    /**
+       \see set_fullscreen(), toggle_fullscreen().
+    **/
+    bool is_fullscreen() const {
+      return _is_fullscreen;
+    }
+
+    //! Return \c true if any key is being pressed on the associated window, \c false otherwise.
+    /**
+       \note The methods below do the same only for specific keys.
+       \see is_event(), is_key(unsigned int) const, is_key(const char *) const, is_key_sequence(), key(), release_key(), set_key(),
+    **/
+    bool is_key() const {
+      return _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;
+    }
+
+    //! Return \c true if key specified by given keycode is being pressed on the associated window, \c false otherwise.
+    /**
+       \param keycode Keycode to test.
+       \note Keycode constants are defined in the cimg namespace and are architecture-dependent. Use them to ensure
+       your code stay portable (see cimg::keyESC).
+       \par Example
+       \code
+       CImgDisplay disp(400,400);
+       while (!disp.is_closed()) {
+         if (disp.key(cimg::keyTAB)) { ... }  // Equivalent to 'if (disp.is_keyTAB())'.
+         disp.wait();
+       }
+       \endcode
+       \see is_event(), is_key() const, is_key(const char *) const, is_key_sequence(), key(), released_key(), set_key(),
+    **/
+    bool is_key(const unsigned int keycode) const {
+#define _cimg_iskey_test(k) if (keycode==cimg::key##k) return _is_key##k;
+      _cimg_iskey_test(ESC); _cimg_iskey_test(F1); _cimg_iskey_test(F2); _cimg_iskey_test(F3);
+      _cimg_iskey_test(F4); _cimg_iskey_test(F5); _cimg_iskey_test(F6); _cimg_iskey_test(F7);
+      _cimg_iskey_test(F8); _cimg_iskey_test(F9); _cimg_iskey_test(F10); _cimg_iskey_test(F11);
+      _cimg_iskey_test(F12); _cimg_iskey_test(PAUSE); _cimg_iskey_test(1); _cimg_iskey_test(2);
+      _cimg_iskey_test(3); _cimg_iskey_test(4); _cimg_iskey_test(5); _cimg_iskey_test(6);
+      _cimg_iskey_test(7); _cimg_iskey_test(8); _cimg_iskey_test(9); _cimg_iskey_test(0);
+      _cimg_iskey_test(BACKSPACE); _cimg_iskey_test(INSERT); _cimg_iskey_test(HOME);
+      _cimg_iskey_test(PAGEUP); _cimg_iskey_test(TAB); _cimg_iskey_test(Q); _cimg_iskey_test(W);
+      _cimg_iskey_test(E); _cimg_iskey_test(R); _cimg_iskey_test(T); _cimg_iskey_test(Y);
+      _cimg_iskey_test(U); _cimg_iskey_test(I); _cimg_iskey_test(O); _cimg_iskey_test(P);
+      _cimg_iskey_test(DELETE); _cimg_iskey_test(END); _cimg_iskey_test(PAGEDOWN);
+      _cimg_iskey_test(CAPSLOCK); _cimg_iskey_test(A); _cimg_iskey_test(S); _cimg_iskey_test(D);
+      _cimg_iskey_test(F); _cimg_iskey_test(G); _cimg_iskey_test(H); _cimg_iskey_test(J);
+      _cimg_iskey_test(K); _cimg_iskey_test(L); _cimg_iskey_test(ENTER);
+      _cimg_iskey_test(SHIFTLEFT); _cimg_iskey_test(Z); _cimg_iskey_test(X); _cimg_iskey_test(C);
+      _cimg_iskey_test(V); _cimg_iskey_test(B); _cimg_iskey_test(N); _cimg_iskey_test(M);
+      _cimg_iskey_test(SHIFTRIGHT); _cimg_iskey_test(ARROWUP); _cimg_iskey_test(CTRLLEFT);
+      _cimg_iskey_test(APPLEFT); _cimg_iskey_test(ALT); _cimg_iskey_test(SPACE); _cimg_iskey_test(ALTGR);
+      _cimg_iskey_test(APPRIGHT); _cimg_iskey_test(MENU); _cimg_iskey_test(CTRLRIGHT);
+      _cimg_iskey_test(ARROWLEFT); _cimg_iskey_test(ARROWDOWN); _cimg_iskey_test(ARROWRIGHT);
+      _cimg_iskey_test(PAD0); _cimg_iskey_test(PAD1); _cimg_iskey_test(PAD2);
+      _cimg_iskey_test(PAD3); _cimg_iskey_test(PAD4); _cimg_iskey_test(PAD5);
+      _cimg_iskey_test(PAD6); _cimg_iskey_test(PAD7); _cimg_iskey_test(PAD8);
+      _cimg_iskey_test(PAD9); _cimg_iskey_test(PADADD); _cimg_iskey_test(PADSUB);
+      _cimg_iskey_test(PADMUL); _cimg_iskey_test(PADDIV);
+      return false;
+    }
+
+    //! Return \c true if key specified by given keycode is being pressed on the associated window, \c false otherwise.
+    /**
+       \param keycode C-string containing the keycode label of the key to test.
+       \note Use it when the key you want to test can be dynamically set by the user.
+       \par Example
+       \code
+       CImgDisplay disp(400,400);
+       const char *const keycode = "TAB";
+       while (!disp.is_closed()) {
+         if (disp.is_key(keycode)) { ... }  // Equivalent to 'if (disp.is_keyTAB())'.
+         disp.wait();
+       }
+       \endcode
+       \see is_event(), is_key() const, is_key(unsigned int) const, is_key_sequence(), key(), released_key(), set_key(),
+    **/
+    bool is_key(const char *const keycode) const {
+#define _cimg_iskey_test2(k) if (!cimg::strcasecmp(keycode,#k)) return _is_key##k;
+      _cimg_iskey_test2(ESC); _cimg_iskey_test2(F1); _cimg_iskey_test2(F2); _cimg_iskey_test2(F3);
+      _cimg_iskey_test2(F4); _cimg_iskey_test2(F5); _cimg_iskey_test2(F6); _cimg_iskey_test2(F7);
+      _cimg_iskey_test2(F8); _cimg_iskey_test2(F9); _cimg_iskey_test2(F10); _cimg_iskey_test2(F11);
+      _cimg_iskey_test2(F12); _cimg_iskey_test2(PAUSE); _cimg_iskey_test2(1); _cimg_iskey_test2(2);
+      _cimg_iskey_test2(3); _cimg_iskey_test2(4); _cimg_iskey_test2(5); _cimg_iskey_test2(6);
+      _cimg_iskey_test2(7); _cimg_iskey_test2(8); _cimg_iskey_test2(9); _cimg_iskey_test2(0);
+      _cimg_iskey_test2(BACKSPACE); _cimg_iskey_test2(INSERT); _cimg_iskey_test2(HOME);
+      _cimg_iskey_test2(PAGEUP); _cimg_iskey_test2(TAB); _cimg_iskey_test2(Q); _cimg_iskey_test2(W);
+      _cimg_iskey_test2(E); _cimg_iskey_test2(R); _cimg_iskey_test2(T); _cimg_iskey_test2(Y);
+      _cimg_iskey_test2(U); _cimg_iskey_test2(I); _cimg_iskey_test2(O); _cimg_iskey_test2(P);
+      _cimg_iskey_test2(DELETE); _cimg_iskey_test2(END); _cimg_iskey_test2(PAGEDOWN);
+      _cimg_iskey_test2(CAPSLOCK); _cimg_iskey_test2(A); _cimg_iskey_test2(S); _cimg_iskey_test2(D);
+      _cimg_iskey_test2(F); _cimg_iskey_test2(G); _cimg_iskey_test2(H); _cimg_iskey_test2(J);
+      _cimg_iskey_test2(K); _cimg_iskey_test2(L); _cimg_iskey_test2(ENTER);
+      _cimg_iskey_test2(SHIFTLEFT); _cimg_iskey_test2(Z); _cimg_iskey_test2(X); _cimg_iskey_test2(C);
+      _cimg_iskey_test2(V); _cimg_iskey_test2(B); _cimg_iskey_test2(N); _cimg_iskey_test2(M);
+      _cimg_iskey_test2(SHIFTRIGHT); _cimg_iskey_test2(ARROWUP); _cimg_iskey_test2(CTRLLEFT);
+      _cimg_iskey_test2(APPLEFT); _cimg_iskey_test2(ALT); _cimg_iskey_test2(SPACE); _cimg_iskey_test2(ALTGR);
+      _cimg_iskey_test2(APPRIGHT); _cimg_iskey_test2(MENU); _cimg_iskey_test2(CTRLRIGHT);
+      _cimg_iskey_test2(ARROWLEFT); _cimg_iskey_test2(ARROWDOWN); _cimg_iskey_test2(ARROWRIGHT);
+      _cimg_iskey_test2(PAD0); _cimg_iskey_test2(PAD1); _cimg_iskey_test2(PAD2);
+      _cimg_iskey_test2(PAD3); _cimg_iskey_test2(PAD4); _cimg_iskey_test2(PAD5);
+      _cimg_iskey_test2(PAD6); _cimg_iskey_test2(PAD7); _cimg_iskey_test2(PAD8);
+      _cimg_iskey_test2(PAD9); _cimg_iskey_test2(PADADD); _cimg_iskey_test2(PADSUB);
+      _cimg_iskey_test2(PADMUL); _cimg_iskey_test2(PADDIV);
+      return false;
+    }
+
+    //! Return \c true if specified key sequence has been typed on the associated window, \c false otherwise.
+    /**
+       \param keycodes_sequence Buffer of keycodes to test.
+       \param length Number of keys in the \c keycodes_sequence buffer.
+       \param remove_sequence Flag telling if the key sequence must be removed from the key history, if found.
+       \note Keycode constants are defined in the cimg namespace and are architecture-dependent. Use them to ensure
+       your code stay portable (see cimg::keyESC).
+       \par Example
+       \code
+       CImgDisplay disp(400,400);
+       const unsigned int key_seq[] = { cimg::keyCTRLLEFT, cimg::keyD };
+       while (!disp.is_closed()) {
+         if (disp.is_key_sequence(key_seq,2)) { ... }  // Test for the 'CTRL+D' keyboard event.
+         disp.wait();
+       }
+       \endcode
+       \see is_event(), is_key() const, is_key(unsigned int) const, is_key(const char *) const, key(), released_key(), set_key(),
+    **/
+    bool is_key_sequence(const unsigned int *const keycodes_sequence, const unsigned int length, const bool remove_sequence=false) {
+      if (keycodes_sequence && length) {
+        const unsigned int
+          *const ps_end = keycodes_sequence + length - 1,
+          *const pk_end = (unsigned int*)_keys + 1 + sizeof(_keys)/sizeof(unsigned int) - length,
+          k = *ps_end;
+        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<length; ++i) res = (*(--ps)==*(pk2++));
+            if (res) {
+              if (remove_sequence) std::memset((void*)(pk-1),0,sizeof(unsigned int)*length);
+              return true;
+            }
+          }
+        }
+      }
+      return false;
+    }
+
+#define _cimg_iskey_def(k) \
+    bool is_key##k() const { \
+      return _is_key##k; \
+    }
+
+    //! Return \c true if the \c ESC key is being pressed on the associated window, \c false otherwise.
+    /**
+       \note Similar methods exist for all keys managed by \CImg (see cimg::keyESC).
+       \see is_event(), is_key(unsigned int) const, is_key(const char *) const, is_key_sequence(), key(), release_key(), set_key(),
+    **/
+    _cimg_iskey_def(ESC); _cimg_iskey_def(F1); _cimg_iskey_def(F2); _cimg_iskey_def(F3);
+    _cimg_iskey_def(F4); _cimg_iskey_def(F5); _cimg_iskey_def(F6); _cimg_iskey_def(F7);
+    _cimg_iskey_def(F8); _cimg_iskey_def(F9); _cimg_iskey_def(F10); _cimg_iskey_def(F11);
+    _cimg_iskey_def(F12); _cimg_iskey_def(PAUSE); _cimg_iskey_def(1); _cimg_iskey_def(2);
+    _cimg_iskey_def(3); _cimg_iskey_def(4); _cimg_iskey_def(5); _cimg_iskey_def(6);
+    _cimg_iskey_def(7); _cimg_iskey_def(8); _cimg_iskey_def(9); _cimg_iskey_def(0);
+    _cimg_iskey_def(BACKSPACE); _cimg_iskey_def(INSERT); _cimg_iskey_def(HOME);
+    _cimg_iskey_def(PAGEUP); _cimg_iskey_def(TAB); _cimg_iskey_def(Q); _cimg_iskey_def(W);
+    _cimg_iskey_def(E); _cimg_iskey_def(R); _cimg_iskey_def(T); _cimg_iskey_def(Y);
+    _cimg_iskey_def(U); _cimg_iskey_def(I); _cimg_iskey_def(O); _cimg_iskey_def(P);
+    _cimg_iskey_def(DELETE); _cimg_iskey_def(END); _cimg_iskey_def(PAGEDOWN);
+    _cimg_iskey_def(CAPSLOCK); _cimg_iskey_def(A); _cimg_iskey_def(S); _cimg_iskey_def(D);
+    _cimg_iskey_def(F); _cimg_iskey_def(G); _cimg_iskey_def(H); _cimg_iskey_def(J);
+    _cimg_iskey_def(K); _cimg_iskey_def(L); _cimg_iskey_def(ENTER);
+    _cimg_iskey_def(SHIFTLEFT); _cimg_iskey_def(Z); _cimg_iskey_def(X); _cimg_iskey_def(C);
+    _cimg_iskey_def(V); _cimg_iskey_def(B); _cimg_iskey_def(N); _cimg_iskey_def(M);
+    _cimg_iskey_def(SHIFTRIGHT); _cimg_iskey_def(ARROWUP); _cimg_iskey_def(CTRLLEFT);
+    _cimg_iskey_def(APPLEFT); _cimg_iskey_def(ALT); _cimg_iskey_def(SPACE); _cimg_iskey_def(ALTGR);
+    _cimg_iskey_def(APPRIGHT); _cimg_iskey_def(MENU); _cimg_iskey_def(CTRLRIGHT);
+    _cimg_iskey_def(ARROWLEFT); _cimg_iskey_def(ARROWDOWN); _cimg_iskey_def(ARROWRIGHT);
+    _cimg_iskey_def(PAD0); _cimg_iskey_def(PAD1); _cimg_iskey_def(PAD2);
+    _cimg_iskey_def(PAD3); _cimg_iskey_def(PAD4); _cimg_iskey_def(PAD5);
+    _cimg_iskey_def(PAD6); _cimg_iskey_def(PAD7); _cimg_iskey_def(PAD8);
+    _cimg_iskey_def(PAD9); _cimg_iskey_def(PADADD); _cimg_iskey_def(PADSUB);
+    _cimg_iskey_def(PADMUL); _cimg_iskey_def(PADDIV);
+
+    //@}
+    //------------------------------------------
+    //
+    //! \name Instance Characteristics
+    //@{
+    //------------------------------------------
+
+#if cimg_display==0
+
+    //! Return width of the screen (current resolution along the X-axis).
+    /**
+       \see screen_height().
+    **/
+    static int screen_width() {
+      _no_display_exception();
+      return 0;
+    }
+
+    //! Return height of the screen (current resolution along the Y-axis).
+    /**
+       \see screen_width().
+    **/
+    static int screen_height() {
+      _no_display_exception();
+      return 0;
+    }
+
+#endif
+
+    //! Return display width.
+    /**
+       \note The width of the display (i.e. the width of the pixel data buffer associated to the CImgDisplay instance)
+       may be different from the actual width of the associated window.
+       \see height(), window_width().
+    **/
+    int width() const {
+      return (int)_width;
+    }
+
+    //! Return display height.
+    /**
+       \note The height of the display (i.e. the height of the pixel data buffer associated to the CImgDisplay instance)
+       may be different from the actual height of the associated window.
+       \see width(), window_height().
+    **/
+    int height() const {
+      return (int)_height;
+    }
+
+    //! Return normalization type of the display.
+    /**
+       The normalization type tells about how the values of an input image are normalized by the CImgDisplay to be correctly displayed.
+       The range of values for pixels displayed on screen is <tt>[0,255]</tt>. If the range of values of the data to display
+       is different, a normalization may be required for displaying the data in a correct way.
+       The normalization type can be one of :
+       - \c 0 : Value normalization is disabled. It is then assumed that all input data to be displayed by the CImgDisplay instance
+       have values in range <tt>[0,255]</tt>.
+       - \c 1 : Value normalization is always performed (this is the default behavior).
+       Before displaying an input image, its values will be (virtually) stretched
+       in range <tt>[0,255]</tt>, so that the contrast of the displayed pixels will be maximum.
+       Use this mode for images whose minimum and maximum values are not prescribed to known values (e.g. float-valued images).
+       Note that when normalized versions of images are computed for display purposes, the actual values of these images are not modified.
+       - \c 2 : Value normalization is performed once (on the first image display), then the same normalization coefficients are kept for
+       next displayed frames.
+       - \c 3 : Value normalization depends on the pixel type of the data to display. For integer pixel types, the normalization
+       is done regarding the minimum/maximum values of the type (no normalization occurs then for <tt>unsigned char</tt>).
+       For float-valued pixel types, the normalization is done regarding the minimum/maximum value of the image data instead.
+
+       \see set_normalization().
+    **/
+    unsigned int normalization() const {
+      return _normalization;
+    }
+
+    //! Return title of the associated window as a C-string.
+    /**
+       \note Window title may be not visible, depending on the used window manager or if the current display is in fullscreen mode.
+       \see set_title().
+    **/
+    const char *title() const {
+      return _title;
+    }
+
+    //! Return width of the associated window.
+    /**
+       \note The width of the display (i.e. the width of the pixel data buffer associated to the CImgDisplay instance)
+       may be different from the actual width of the associated window.
+       \see window_height(), width().
+    **/
+    int window_width() const {
+      return (int)_window_width;
+    }
+
+    //! Return height of the associated window.
+    /**
+       \note The height of the display (i.e. the height of the pixel data buffer associated to the CImgDisplay instance)
+       may be different from the actual height of the associated window.
+       \see window_width(), height().
+    **/
+    int window_height() const {
+      return (int)_window_height;
+    }
+
+    //! Return X-coordinate of the associated window.
+    /**
+       \note The returned coordinate corresponds to the location of the upper-left corner of the associated window.
+       \see window_y().
+    **/
+    int window_x() const {
+      return _window_x;
+    }
+
+    //! Return Y-coordinate of the associated window.
+    /**
+       \note The returned coordinate corresponds to the location of the upper-left corner of the associated window.
+       \see window_x().
+    **/
+    int window_y() const {
+      return _window_y;
+    }
+
+    //! Return X-coordinate of the mouse pointer.
+    /**
+       \note
+       - If the mouse pointer is outside window area, \c -1 is returned.
+       - Otherwise, the returned value is in the range [0,width()-1].
+       \see mouse_y(), button(), wheel().
+    **/
+    int mouse_x() const {
+      return _mouse_x;
+    }
+
+    //! Return Y-coordinate of the mouse pointer.
+    /**
+       \note
+       - If the mouse pointer is outside window area, \c -1 is returned.
+       - Otherwise, the returned value is in the range [0,height()-1].
+       \see mouse_x(), button(), wheel().
+    **/
+    int mouse_y() const {
+      return _mouse_y;
+    }
+
+    //! Return current state of the mouse buttons.
+    /**
+       \note Three mouse buttons can be managed. If one button is pressed, its corresponding bit in the returned value is set :
+       - bit \c 0 (value \c 0x1) : State of the left mouse button.
+       - bit \c 1 (value \c 0x2) : State of the right mouse button.
+       - bit \c 2 (value \c 0x4) : State of the middle mouse button.
+
+       Several bits can be activated if more than one button are pressed at the same time.
+       \par Example
+       \code
+       CImgDisplay disp(400,400);
+       while (!disp.is_closed()) {
+         if (disp.button()&1) { // Left button clicked.
+           ...
+         }
+         if (disp.button()&2) { // Right button clicked.
+           ...
+         }
+         if (disp.button()&4) { // Middle button clicked.
+           ...
+         }
+         disp.wait();
+       }
+       \endcode
+       \see mouse_x(), mouse_y(), wheel().
+    **/
+    unsigned int button() const {
+      return _button;
+    }
+
+    //! Return current state of the mouse wheel.
+    /**
+       \note
+       - The returned value can be positive or negative depending on whether the mouse wheel has been scrolled forward or backward.
+       - Scrolling the wheel forward add \c 1 to the wheel value.
+       - Scrolling the wheel backward substract \c 1 to the wheel value.
+       - The returned value cumulates the number of forward of backward scrolls since the creation of the display, or since the
+       last reset of the wheel value (using set_wheel()). It is strongly recommended to quickly reset the wheel counter
+       when an action has been performed regarding the current wheel value. Otherwise, the returned wheel value may be for instance \c 0
+       despite the fact that many scrolls have been done (as many in forward as in backward directions).
+       \par Example
+       \code
+       CImgDisplay disp(400,400);
+       while (!disp.is_closed()) {
+         if (disp.wheel()) {
+           int counter = disp.wheel();  // Read the state of the mouse wheel.
+           ...                          // Do what you want with 'counter'.
+           disp.set_wheel();            // Reset the wheel value to 0.
+         }
+         disp.wait();
+       }
+       \endcode
+       \see mouse_x(), mouse_y(), button(), set_wheel().
+    **/
+    int wheel() const {
+      return _wheel;
+    }
+
+    //! Return one entry from the pressed keys history.
+    /**
+       \param pos Indice to read from the pressed keys history (indice \c 0 corresponds to latest entry).
+       \return Keycode of a pressed key or \c 0 for a released key.
+       \note
+       - Each CImgDisplay stores a history of the pressed keys in a buffer of size \c 128. When a new key is pressed,
+       its keycode is stored in the pressed keys history. When a key is released, \c 0 is put instead.
+       This means that up to the 64 last pressed keys may be read from the pressed keys history.
+       When a new value is stored, the pressed keys history is shifted so that the latest entry is always
+       stored at position \c 0.
+       - Keycode constants are defined in the cimg namespace and are architecture-dependent. Use them to ensure
+       your code stay portable (see cimg::keyESC).
+
+       \see is_event(), is_key() const, is_key(unsigned int) const, is_key(const char *) const, is_key_sequence(), released_key(), set_key(),
+    **/
+    unsigned int key(const unsigned int pos=0) const {
+      return pos<(sizeof(_keys)/sizeof(unsigned int))?_keys[pos]:0;
+    }
+
+    //! Return one entry from the released keys history.
+    /**
+       \param pos Indice to read from the released keys history (indice \c 0 corresponds to latest entry).
+       \return Keycode of a released key or \c 0 for a pressed key.
+       \note
+       - Each CImgDisplay stores a history of the released keys in a buffer of size \c 128. When a new key is released,
+       its keycode is stored in the pressed keys history. When a key is pressed, \c 0 is put instead.
+       This means that up to the 64 last released keys may be read from the released keys history.
+       When a new value is stored, the released keys history is shifted so that the latest entry is always
+       stored at position \c 0.
+       - Keycode constants are defined in the cimg namespace and are architecture-dependent. Use them to ensure
+       your code stay portable (see cimg::keyESC).
+
+       \see is_event(), is_key() const, is_key(unsigned int) const, is_key(const char *) const, is_key_sequence(), released_key(), set_key(),
+    **/
+    unsigned int released_key(const unsigned int pos=0) const {
+      return pos<(sizeof(_released_keys)/sizeof(unsigned int))?_released_keys[pos]:0;
+    }
+
+    //! Return keycode corresponding to the specified string.
+    /**
+       \note Keycode constants are defined in the cimg namespace and are architecture-dependent. Use them to ensure
+       your code stay portable (see cimg::keyESC).
+       \par Example
+       \code
+       const unsigned int keyTAB = CImgDisplay::keycode("TAB");  // Return cimg::keyTAB.
+       \endcode
+    **/
+    static unsigned int keycode(const char *const keycode) {
+#define _cimg_keycode(k) if (!cimg::strcasecmp(keycode,#k)) return cimg::key##k;
+      _cimg_keycode(ESC); _cimg_keycode(F1); _cimg_keycode(F2); _cimg_keycode(F3);
+      _cimg_keycode(F4); _cimg_keycode(F5); _cimg_keycode(F6); _cimg_keycode(F7);
+      _cimg_keycode(F8); _cimg_keycode(F9); _cimg_keycode(F10); _cimg_keycode(F11);
+      _cimg_keycode(F12); _cimg_keycode(PAUSE); _cimg_keycode(1); _cimg_keycode(2);
+      _cimg_keycode(3); _cimg_keycode(4); _cimg_keycode(5); _cimg_keycode(6);
+      _cimg_keycode(7); _cimg_keycode(8); _cimg_keycode(9); _cimg_keycode(0);
+      _cimg_keycode(BACKSPACE); _cimg_keycode(INSERT); _cimg_keycode(HOME);
+      _cimg_keycode(PAGEUP); _cimg_keycode(TAB); _cimg_keycode(Q); _cimg_keycode(W);
+      _cimg_keycode(E); _cimg_keycode(R); _cimg_keycode(T); _cimg_keycode(Y);
+      _cimg_keycode(U); _cimg_keycode(I); _cimg_keycode(O); _cimg_keycode(P);
+      _cimg_keycode(DELETE); _cimg_keycode(END); _cimg_keycode(PAGEDOWN);
+      _cimg_keycode(CAPSLOCK); _cimg_keycode(A); _cimg_keycode(S); _cimg_keycode(D);
+      _cimg_keycode(F); _cimg_keycode(G); _cimg_keycode(H); _cimg_keycode(J);
+      _cimg_keycode(K); _cimg_keycode(L); _cimg_keycode(ENTER);
+      _cimg_keycode(SHIFTLEFT); _cimg_keycode(Z); _cimg_keycode(X); _cimg_keycode(C);
+      _cimg_keycode(V); _cimg_keycode(B); _cimg_keycode(N); _cimg_keycode(M);
+      _cimg_keycode(SHIFTRIGHT); _cimg_keycode(ARROWUP); _cimg_keycode(CTRLLEFT);
+      _cimg_keycode(APPLEFT); _cimg_keycode(ALT); _cimg_keycode(SPACE); _cimg_keycode(ALTGR);
+      _cimg_keycode(APPRIGHT); _cimg_keycode(MENU); _cimg_keycode(CTRLRIGHT);
+      _cimg_keycode(ARROWLEFT); _cimg_keycode(ARROWDOWN); _cimg_keycode(ARROWRIGHT);
+      _cimg_keycode(PAD0); _cimg_keycode(PAD1); _cimg_keycode(PAD2);
+      _cimg_keycode(PAD3); _cimg_keycode(PAD4); _cimg_keycode(PAD5);
+      _cimg_keycode(PAD6); _cimg_keycode(PAD7); _cimg_keycode(PAD8);
+      _cimg_keycode(PAD9); _cimg_keycode(PADADD); _cimg_keycode(PADSUB);
+      _cimg_keycode(PADMUL); _cimg_keycode(PADDIV);
+      return 0;
+    }
+
+    //! Return the current refresh rate, in frames per second.
+    /**
+       \note Returns a significant value when the current instance is used to display successive frames.
+       It measures the delay between successive calls to frames_per_second().
+       \see wait(), wait(unsigned int), cimg::wait().
+    **/
+    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;
+    }
+
+    //@}
+    //---------------------------------------
+    //
+    //! \name Window Manipulation
+    //@{
+    //---------------------------------------
+
+#if cimg_display==0
+
+    //! Display image on associated window.
+    /**
+       \param img Input image to display.
+       \note This method returns immediately.
+       \see display(const CImgList<T>&,char,float), CImg<T>::display(const char *,bool) const.
+    **/
+    template<typename T>
+    CImgDisplay& display(const CImg<T>& img) {
+      return assign(img);
+    }
+
+#endif
+
+    //! Display list of images on associated window.
+    /**
+       \param list List of images to display.
+       \param axis Axis used to append the images along, for the visualization (can be \c x, \c y, \c z or \c c).
+       \param align Relative position of aligned images when displaying lists with images of different sizes
+       (\c 0 for upper-left, \c 0.5 for centering and \c 1 for lower-right).
+       \note This method returns immediately.
+       \see display(const CImg<T>&).
+    **/
+    template<typename T>
+    CImgDisplay& display(const CImgList<T>& list, const char axis='x', const float align=0) {
+      return display(list.get_append(axis,align));
+    }
+
+#if cimg_display==0
+
+    //! Show (closed) associated window on the screen.
+    /**
+       \note
+       - Force the associated window of a display to be visible on the screen, even if it has been closed before.
+       - Using show() on a visible display does nothing.
+       \see close().
+    **/
+    CImgDisplay& show() {
+      return assign();
+    }
+
+    //! Close (visible) associated window and make it disappear from the screen.
+    /**
+       \note
+       - A closed display only means the associated window is not visible anymore. This does not mean the display has been destroyed.
+       Use show() to make the associated window reappear.
+       - Using close() on a closed display does nothing.
+       \see show().
+    **/
+    CImgDisplay& close() {
+      return assign();
+    }
+
+    //! Move associated window to a new location.
+    /**
+       \param pos_x X-coordinate of the new window location.
+       \param pos_y Y-coordinate of the new window location.
+       \note Depending on the window manager behavior, this method may not succeed (no exceptions are thrown nevertheless).
+    **/
+    CImgDisplay& move(const int pos_x, const int pos_y) {
+      return assign(pos_x,pos_y);
+    }
+
+#endif
+
+    //! Resize display to the size of the associated window.
+    /**
+       \param force_redraw Flag telling if the previous window content must be updated and refreshed as well.
+       \note
+       - Calling this method ensures that width() and window_width() become equal, as well as height() and window_height().
+       - The associated window is also resized to specified dimensions.
+       \see width(), height(), window_width(), window_height(), resize(int,int,bool), resize(const CImg<T>&,bool), resize(const CImgDisplay&,bool).
+    **/
+    CImgDisplay& resize(const bool force_redraw=true) {
+      resize(_window_width,_window_height,force_redraw);
+      return *this;
+    }
+
+#if cimg_display==0
+
+    //! Resize display to the specified size.
+    /**
+       \param width Requested display width.
+       \param height Requested display height.
+       \param force_redraw Flag telling if the previous window content must be updated and refreshed as well.
+       \note The associated window is also resized to specified dimensions.
+       \see width(), height(), resize(bool), resize(const CImg<T>&,bool), resize(const CImgDisplay&,bool).
+    **/
+    CImgDisplay& resize(const int width, const int height, const bool force_redraw=true) {
+      return assign(width,height,0,3,force_redraw);
+    }
+
+#endif
+
+    //! Resize display to the size of an input image.
+    /**
+       \param img Input image to take size from.
+       \param force_redraw Flag telling if the previous window content must be resized and updated as well.
+       \note
+       - Calling this method ensures that width() and <tt>img.width()</tt> become equal, as well as height() and <tt>img.height()</tt>.
+       - The associated window is also resized to specified dimensions.
+       \see width(), height(), resize(bool), resize(int,int,bool), resize(const CImgDisplay&,bool).
+    **/
+    template<typename T>
+    CImgDisplay& resize(const CImg<T>& img, const bool force_redraw=true) {
+      return resize(img._width,img._height,force_redraw);
+    }
+
+    //! Resize display to the size of another CImgDisplay instance.
+    /**
+       \param disp Input display to take size from.
+       \param force_redraw Flag telling if the previous window content must be resized and updated as well.
+       \note
+       - Calling this method ensures that width() and <tt>disp.width()</tt> become equal, as well as height() and <tt>disp.height()</tt>.
+       - The associated window is also resized to specified dimensions.
+       \see width(), height(), resize(bool), resize(int,int,bool), resize(const CImg&,bool).
+    **/
+    CImgDisplay& resize(const CImgDisplay& disp, const bool force_redraw=true) {
+      return resize(disp._width,disp._height,force_redraw);
+    }
+
+    // Render pixel buffer with size (wd,hd) from source buffer of size (ws,hs) (internal).
+    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; std::memcpy(ptrd,ptrd - wd,sizeof(t)*wd), ++y, ptrd+=wd, dy = *(poffy++)) {}
+        ptrs+=dy;
+      }
+      delete[] offx; delete[] offy;
+    }
+
+    //! Set normalization type.
+    /**
+       \param normalization New normalization mode.
+       \see normalization().
+    **/
+    CImgDisplay& set_normalization(const unsigned int normalization) {
+      _normalization = normalization;
+      _min = _max = 0;
+      return *this;
+    }
+
+#if cimg_display==0
+
+    //! Set title of the associated window.
+    /**
+       \param format C-string containing the format of the title, as with <tt>std::printf()</tt>.
+       \warning As the first argument is a format string, it is highly recommended to write
+       \code
+       disp.set_title("%s",window_title);
+       \endcode
+       instead of
+       \code
+       disp.set_title(window_title);
+       \endcode
+       if \c window_title can be arbitrary, to prevent nasty memory access.
+       \see title().
+    **/
+    CImgDisplay& set_title(const char *const format, ...) {
+      return assign(0,0,format);
+    }
+
+#endif
+
+    //! Enable or disable fullscreen mode.
+    /**
+       \param is_fullscreen Flag telling is the fullscreen mode must be activated or not.
+       \param force_redraw Flag telling if the previous window content must be displayed as well.
+       \note
+       - When the fullscreen mode is enabled, the associated window fills the entire screen but the size of the current display
+       is not modified.
+       - The screen resolution may be switched to fit the associated window size and ensure it appears the largest as possible.
+       For X-Window (X11) users, the configuration flag \c cimg_use_xrandr has to be set to allow the screen resolution change
+       (requires the X11 extensions to be enabled).
+       \see toggle_fullscreen().
+    **/
+    CImgDisplay& set_fullscreen(const bool is_fullscreen, const bool force_redraw=true) {
+      if (is_empty() || _is_fullscreen==is_fullscreen) return *this;
+      return toggle_fullscreen(force_redraw);
+    }
+
+#if cimg_display==0
+
+    //! Toggle fullscreen mode.
+    /**
+       \param force_redraw Flag telling if the previous window content must be displayed as well.
+       \note Enable fullscreen mode if it was not enabled, and disable it otherwise.
+       \see set_fullscreen().
+    **/
+    CImgDisplay& toggle_fullscreen(const bool force_redraw=true) {
+      return assign(_width,_height,0,3,force_redraw);
+    }
+
+    //! Show mouse pointer.
+    /**
+       \note Depending on the window manager behavior, this method may not succeed (no exceptions are thrown nevertheless).
+       \see hide_mouse(), set_mouse().
+    **/
+    CImgDisplay& show_mouse() {
+      return assign();
+    }
+
+    //! Hide mouse pointer.
+    /**
+       \note Depending on the window manager behavior, this method may not succeed (no exceptions are thrown nevertheless).
+       \see show_mouse(), set_mouse().
+    **/
+    CImgDisplay& hide_mouse() {
+      return assign();
+    }
+
+    //! Move mouse pointer to a specified location.
+    /**
+       \note Depending on the window manager behavior, this method may not succeed (no exceptions are thrown nevertheless).
+       \see show_mouse(), hide_mouse(), mouse_x(), mouse_y().
+    **/
+    CImgDisplay& set_mouse(const int pos_x, const int pos_y) {
+      return assign(pos_x,pos_y);
+    }
+
+#endif
+
+    //! Simulate a mouse button release event.
+    /**
+       \note All mouse buttons are considered released at the same time.
+       \see button(), set_mouse(int,int), set_button(unsigned int,bool).
+    **/
+    CImgDisplay& set_button() {
+      _button = 0;
+      _is_event = true;
+      return *this;
+    }
+
+    //! Simulate a mouse button press or release event.
+    /**
+       \param button Buttons event code, where each button is associated to a single bit.
+       \param is_pressed Flag telling if the mouse button is considered as pressed or released.
+       \see button(), set_mouse(), set_button(unsigned int,bool).
+    **/
+    CImgDisplay& set_button(const unsigned int button, const bool is_pressed=true) {
+      const unsigned int buttoncode = button==1?1:button==2?2:button==3?4:0;
+      if (is_pressed) _button |= buttoncode; else _button &= ~buttoncode;
+      _is_event = buttoncode?true:false;
+      return *this;
+    }
+
+    //! Flush all mouse wheel events.
+    /**
+       \note Make wheel() to return \c 0, if called afterwards.
+       \see wheel(), set_wheel(int).
+    **/
+    CImgDisplay& set_wheel() {
+      _wheel = 0;
+      _is_event = true;
+      return *this;
+    }
+
+    //! Simulate a wheel event.
+    /**
+       \param amplitude Amplitude of the wheel scrolling to simulate.
+       \note Make wheel() to return \c amplitude, if called afterwards.
+       \see wheel(), set_wheel().
+    **/
+    CImgDisplay& set_wheel(const int amplitude) {
+      _wheel+=amplitude;
+      _is_event = amplitude?true:false;
+      return *this;
+    }
+
+    //! Flush all key events.
+    /**
+       \note Make key() to return \c 0, if called afterwards.
+       \see key(), set_key(unsigned int,bool).
+    **/
+    CImgDisplay& set_key() {
+      std::memset((void*)_keys,0,sizeof(_keys));
+      std::memset((void*)_released_keys,0,sizeof(_released_keys));
+      _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_event = true;
+      return *this;
+    }
+
+    //! Simulate a keyboard press/release event.
+    /**
+       \param keycode Keycode of the associated key.
+       \param is_pressed Flag telling if the key is considered as pressed or released.
+       \note Keycode constants are defined in the cimg namespace and are architecture-dependent. Use them to ensure
+       your code stay portable (see cimg::keyESC).
+       \see key(), set_key().
+    **/
+    CImgDisplay& set_key(const unsigned int keycode, const bool is_pressed=true) {
+#define _cimg_set_key(k) if (keycode==cimg::key##k) _is_key##k = is_pressed;
+      _cimg_set_key(ESC); _cimg_set_key(F1); _cimg_set_key(F2); _cimg_set_key(F3);
+      _cimg_set_key(F4); _cimg_set_key(F5); _cimg_set_key(F6); _cimg_set_key(F7);
+      _cimg_set_key(F8); _cimg_set_key(F9); _cimg_set_key(F10); _cimg_set_key(F11);
+      _cimg_set_key(F12); _cimg_set_key(PAUSE); _cimg_set_key(1); _cimg_set_key(2);
+      _cimg_set_key(3); _cimg_set_key(4); _cimg_set_key(5); _cimg_set_key(6);
+      _cimg_set_key(7); _cimg_set_key(8); _cimg_set_key(9); _cimg_set_key(0);
+      _cimg_set_key(BACKSPACE); _cimg_set_key(INSERT); _cimg_set_key(HOME);
+      _cimg_set_key(PAGEUP); _cimg_set_key(TAB); _cimg_set_key(Q); _cimg_set_key(W);
+      _cimg_set_key(E); _cimg_set_key(R); _cimg_set_key(T); _cimg_set_key(Y);
+      _cimg_set_key(U); _cimg_set_key(I); _cimg_set_key(O); _cimg_set_key(P);
+      _cimg_set_key(DELETE); _cimg_set_key(END); _cimg_set_key(PAGEDOWN);
+      _cimg_set_key(CAPSLOCK); _cimg_set_key(A); _cimg_set_key(S); _cimg_set_key(D);
+      _cimg_set_key(F); _cimg_set_key(G); _cimg_set_key(H); _cimg_set_key(J);
+      _cimg_set_key(K); _cimg_set_key(L); _cimg_set_key(ENTER);
+      _cimg_set_key(SHIFTLEFT); _cimg_set_key(Z); _cimg_set_key(X); _cimg_set_key(C);
+      _cimg_set_key(V); _cimg_set_key(B); _cimg_set_key(N); _cimg_set_key(M);
+      _cimg_set_key(SHIFTRIGHT); _cimg_set_key(ARROWUP); _cimg_set_key(CTRLLEFT);
+      _cimg_set_key(APPLEFT); _cimg_set_key(ALT); _cimg_set_key(SPACE); _cimg_set_key(ALTGR);
+      _cimg_set_key(APPRIGHT); _cimg_set_key(MENU); _cimg_set_key(CTRLRIGHT);
+      _cimg_set_key(ARROWLEFT); _cimg_set_key(ARROWDOWN); _cimg_set_key(ARROWRIGHT);
+      _cimg_set_key(PAD0); _cimg_set_key(PAD1); _cimg_set_key(PAD2);
+      _cimg_set_key(PAD3); _cimg_set_key(PAD4); _cimg_set_key(PAD5);
+      _cimg_set_key(PAD6); _cimg_set_key(PAD7); _cimg_set_key(PAD8);
+      _cimg_set_key(PAD9); _cimg_set_key(PADADD); _cimg_set_key(PADSUB);
+      _cimg_set_key(PADMUL); _cimg_set_key(PADDIV);
+      if (is_pressed) {
+        if (*_keys)
+          std::memmove((void*)(_keys+1),(void*)_keys,sizeof(_keys) - sizeof(unsigned int));
+        *_keys = keycode;
+        if (*_released_keys) {
+          std::memmove((void*)(_released_keys+1),(void*)_released_keys,sizeof(_released_keys) - sizeof(unsigned int));
+          *_released_keys = 0;
+        }
+      } else {
+        if (*_keys) {
+          std::memmove((void*)(_keys+1),(void*)_keys,sizeof(_keys) - sizeof(unsigned int));
+          *_keys = 0;
+        }
+        if (*_released_keys)
+          std::memmove((void*)(_released_keys+1),(void*)_released_keys,sizeof(_released_keys) - sizeof(unsigned int));
+        *_released_keys = keycode;
+      }
+      _is_event = keycode?true:false;
+      return *this;
+    }
+
+    //! Flush all display events.
+    /**
+       \note Remove all passed events from the current display.
+       \see set_button(), set_key(), set_wheel().
+    **/
+    CImgDisplay& flush() {
+      set_key().set_button().set_wheel();
+      _is_resized = _is_moved = _is_event = false;
+      _fps_timer = _fps_frames = _timer = 0;
+      _fps_fps = 0;
+      return *this;
+    }
+
+    //! Wait for any user event occuring on the current display.
+    CImgDisplay& wait() {
+      wait(*this);
+      return *this;
+    }
+
+    //! Wait for a given number of milliseconds since the last call to wait().
+    /**
+       \param milliseconds Number of milliseconds to wait for.
+       \note Similar to cimg::wait().
+       \see cimg::wait().
+    **/
+    CImgDisplay& wait(const unsigned int milliseconds) {
+      cimg::_wait(milliseconds,_timer);
+      return *this;
+    }
+
+    //! Wait for any event occuring on the display \c disp1.
+    static void wait(CImgDisplay& disp1) {
+      disp1._is_event = 0;
+      while (!disp1._is_closed && !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_closed || !disp2._is_closed) &&
+             !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_closed || !disp2._is_closed || !disp3._is_closed) &&
+             !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_closed || !disp2._is_closed || !disp3._is_closed || !disp4._is_closed) &&
+             !disp1._is_event && !disp2._is_event && !disp3._is_event && !disp4._is_event) wait_all();
+    }
+
+    //! Wait for any event occuring either on the display \c disp1, \c disp2, \c disp3, \c disp4 or \c disp5.
+    static void wait(CImgDisplay& disp1, CImgDisplay& disp2, CImgDisplay& disp3, CImgDisplay& disp4, CImgDisplay& disp5) {
+      disp1._is_event = disp2._is_event = disp3._is_event = disp4._is_event = disp5._is_event = 0;
+      while ((!disp1._is_closed || !disp2._is_closed || !disp3._is_closed || !disp4._is_closed || !disp5._is_closed) &&
+             !disp1._is_event && !disp2._is_event && !disp3._is_event && !disp4._is_event && !disp5._is_event) wait_all();
+    }
+
+    //! Wait for any event occuring either on the display \c disp1, \c disp2, \c disp3, \c disp4, ... \c disp6.
+    static void wait(CImgDisplay& disp1, CImgDisplay& disp2, CImgDisplay& disp3, CImgDisplay& disp4, CImgDisplay& disp5,
+                     CImgDisplay& disp6) {
+      disp1._is_event = disp2._is_event = disp3._is_event = disp4._is_event = disp5._is_event =
+        disp6._is_event = 0;
+      while ((!disp1._is_closed || !disp2._is_closed || !disp3._is_closed || !disp4._is_closed || !disp5._is_closed ||
+              !disp6._is_closed) &&
+             !disp1._is_event && !disp2._is_event && !disp3._is_event && !disp4._is_event && !disp5._is_event &&
+             !disp6._is_event) wait_all();
+    }
+
+    //! Wait for any event occuring either on the display \c disp1, \c disp2, \c disp3, \c disp4, ... \c disp7.
+    static void wait(CImgDisplay& disp1, CImgDisplay& disp2, CImgDisplay& disp3, CImgDisplay& disp4, CImgDisplay& disp5,
+                     CImgDisplay& disp6, CImgDisplay& disp7) {
+      disp1._is_event = disp2._is_event = disp3._is_event = disp4._is_event = disp5._is_event =
+        disp6._is_event = disp7._is_event = 0;
+      while ((!disp1._is_closed || !disp2._is_closed || !disp3._is_closed || !disp4._is_closed || !disp5._is_closed ||
+              !disp6._is_closed || !disp7._is_closed) &&
+             !disp1._is_event && !disp2._is_event && !disp3._is_event && !disp4._is_event && !disp5._is_event &&
+             !disp6._is_event && !disp7._is_event) wait_all();
+    }
+
+    //! Wait for any event occuring either on the display \c disp1, \c disp2, \c disp3, \c disp4, ... \c disp8.
+    static void wait(CImgDisplay& disp1, CImgDisplay& disp2, CImgDisplay& disp3, CImgDisplay& disp4, CImgDisplay& disp5,
+                     CImgDisplay& disp6, CImgDisplay& disp7, CImgDisplay& disp8) {
+      disp1._is_event = disp2._is_event = disp3._is_event = disp4._is_event = disp5._is_event =
+        disp6._is_event = disp7._is_event = disp8._is_event = 0;
+      while ((!disp1._is_closed || !disp2._is_closed || !disp3._is_closed || !disp4._is_closed || !disp5._is_closed ||
+              !disp6._is_closed || !disp7._is_closed || !disp8._is_closed) &&
+             !disp1._is_event && !disp2._is_event && !disp3._is_event && !disp4._is_event && !disp5._is_event &&
+             !disp6._is_event && !disp7._is_event && !disp8._is_event) wait_all();
+    }
+
+    //! Wait for any event occuring either on the display \c disp1, \c disp2, \c disp3, \c disp4, ... \c disp9.
+    static void wait(CImgDisplay& disp1, CImgDisplay& disp2, CImgDisplay& disp3, CImgDisplay& disp4, CImgDisplay& disp5,
+                     CImgDisplay& disp6, CImgDisplay& disp7, CImgDisplay& disp8, CImgDisplay& disp9) {
+      disp1._is_event = disp2._is_event = disp3._is_event = disp4._is_event = disp5._is_event =
+        disp6._is_event = disp7._is_event = disp8._is_event = disp9._is_event = 0;
+      while ((!disp1._is_closed || !disp2._is_closed || !disp3._is_closed || !disp4._is_closed || !disp5._is_closed ||
+              !disp6._is_closed || !disp7._is_closed || !disp8._is_closed || !disp9._is_closed) &&
+             !disp1._is_event && !disp2._is_event && !disp3._is_event && !disp4._is_event && !disp5._is_event &&
+             !disp6._is_event && !disp7._is_event && !disp8._is_event && !disp9._is_event) wait_all();
+    }
+
+    //! Wait for any event occuring either on the display \c disp1, \c disp2, \c disp3, \c disp4, ... \c disp10.
+    static void wait(CImgDisplay& disp1, CImgDisplay& disp2, CImgDisplay& disp3, CImgDisplay& disp4, CImgDisplay& disp5,
+                     CImgDisplay& disp6, CImgDisplay& disp7, CImgDisplay& disp8, CImgDisplay& disp9, CImgDisplay& disp10) {
+      disp1._is_event = disp2._is_event = disp3._is_event = disp4._is_event = disp5._is_event =
+        disp6._is_event = disp7._is_event = disp8._is_event = disp9._is_event = disp10._is_event = 0;
+      while ((!disp1._is_closed || !disp2._is_closed || !disp3._is_closed || !disp4._is_closed || !disp5._is_closed ||
+              !disp6._is_closed || !disp7._is_closed || !disp8._is_closed || !disp9._is_closed || !disp10._is_closed) &&
+             !disp1._is_event && !disp2._is_event && !disp3._is_event && !disp4._is_event && !disp5._is_event &&
+             !disp6._is_event && !disp7._is_event && !disp8._is_event && !disp9._is_event && !disp10._is_event) wait_all();
+    }
+
+#if cimg_display==0
+
+    //! Wait for any window event occuring in any opened CImgDisplay.
+    static void wait_all() {
+      return _no_display_exception();
+    }
+
+    //! Render image into internal display buffer.
+    /**
+       \param img Input image data to render.
+       \note
+       - Convert image data representation into the internal display buffer (architecture-dependent structure).
+       - The content of the associated window is not modified, until paint() is called.
+       - Should not be used for common CImgDisplay uses, since display() is more useful.
+       \see paint(), snapshot().
+    **/
+    template<typename T>
+    CImgDisplay& render(const CImg<T>& img) {
+      return assign(img);
+    }
+
+    //! Paint internal display buffer on associated window.
+    /**
+       \note
+       - Update the content of the associated window with the internal display buffer, e.g. after a render() call.
+       - Should not be used for common CImgDisplay uses, since display() is more useful.
+       \see render(), snapshot().
+    **/
+    CImgDisplay& paint() {
+      return assign();
+    }
+
+    //! Take a snapshot of the associated window content.
+    /**
+       \param[out] img Output snapshot. Can be empty on input.
+       \see render(), paint().
+    **/
+    template<typename T>
+    const CImgDisplay& snapshot(CImg<T>& img) const {
+      cimg::unused(img);
+      _no_display_exception();
+      return *this;
+    }
+#endif
+
+    // X11-based implementation
+    //--------------------------
+#if cimg_display==1
+
+    Atom _wm_window_atom, _wm_protocol_atom;
+    Window _window, _background_window;
+    Colormap _colormap;
+    XImage *_image;
+    void *_data;
+#ifdef cimg_use_xshm
+    XShmSegmentInfo *_shminfo;
+#endif
+
+    static int screen_width() {
+      Display *const dpy = cimg::X11_attr().display;
+      int res = 0;
+      if (!dpy) {
+        Display *const _dpy = XOpenDisplay(0);
+        if (!_dpy)
+          throw CImgDisplayException("CImgDisplay::screen_width() : Failed to open X11 display.");
+        res = DisplayWidth(_dpy,DefaultScreen(_dpy));
+        XCloseDisplay(_dpy);
+      } else {
+#ifdef cimg_use_xrandr
+        if (cimg::X11_attr().resolutions && cimg::X11_attr().curr_resolution)
+          res = cimg::X11_attr().resolutions[cimg::X11_attr().curr_resolution].width;
+        else res = DisplayWidth(dpy,DefaultScreen(dpy));
+#else
+        res = DisplayWidth(dpy,DefaultScreen(dpy));
+#endif
+      }
+      return res;
+    }
+
+    static int screen_height() {
+      Display *const dpy = cimg::X11_attr().display;
+      int res = 0;
+      if (!dpy) {
+        Display *const _dpy = XOpenDisplay(0);
+        if (!_dpy)
+          throw CImgDisplayException("CImgDisplay::screen_height() : Failed to open X11 display.");
+        res = DisplayHeight(_dpy,DefaultScreen(_dpy));
+        XCloseDisplay(_dpy);
+      } else {
+#ifdef cimg_use_xrandr
+        if (cimg::X11_attr().resolutions && cimg::X11_attr().curr_resolution)
+          res = cimg::X11_attr().resolutions[cimg::X11_attr().curr_resolution].height;
+        else res = DisplayHeight(dpy,DefaultScreen(dpy));
+#else
+        res = DisplayHeight(dpy,DefaultScreen(dpy));
+#endif
+      }
+      return res;
+    }
+
+    static void wait_all() {
+      Display *const dpy = cimg::X11_attr().display;
+      if (!dpy) return;
+      XLockDisplay(dpy);
+      bool flag = true;
+      XEvent event;
+      while (flag) {
+        XNextEvent(dpy,&event);
+        for (unsigned int i = 0; i<cimg::X11_attr().nb_wins; ++i)
+          if (!cimg::X11_attr().wins[i]->_is_closed && event.xany.window==cimg::X11_attr().wins[i]->_window) {
+            cimg::X11_attr().wins[i]->_handle_events(&event);
+            if (cimg::X11_attr().wins[i]->_is_event) flag = false;
+          }
+      }
+      XUnlockDisplay(dpy);
+    }
+
+    void _handle_events(const XEvent *const pevent) {
+      Display *const dpy = cimg::X11_attr().display;
+      XEvent event = *pevent;
+      switch (event.type) {
+      case ClientMessage : {
+        if ((int)event.xclient.message_type==(int)_wm_protocol_atom &&
+            (int)event.xclient.data.l[0]==(int)_wm_window_atom) {
+          XUnmapWindow(cimg::X11_attr().display,_window);
+          _is_closed = _is_event = true;
+        }
+      } break;
+      case ConfigureNotify : {
+        while (XCheckWindowEvent(dpy,_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(dpy,_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(dpy,_window,ExposureMask,&event)) {}
+        _paint(false);
+        if (_is_fullscreen) {
+          XWindowAttributes attr;
+          XGetWindowAttributes(dpy,_window,&attr);
+          while (attr.map_state!=IsViewable) XSync(dpy,0);
+          XSetInputFocus(dpy,_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>=width() || _mouse_y>=height()) _mouse_x = _mouse_y = -1;
+          switch (event.xbutton.button) {
+          case 1 : set_button(1); break;
+          case 3 : set_button(2); break;
+          case 2 : set_button(3); break;
+          }
+        } while (XCheckWindowEvent(dpy,_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>=width() || _mouse_y>=height()) _mouse_x = _mouse_y = -1;
+          switch (event.xbutton.button) {
+          case 1 : set_button(1,false); break;
+          case 3 : set_button(2,false); break;
+          case 2 : set_button(3,false); break;
+          case 4 : set_wheel(1); break;
+          case 5 : set_wheel(-1); break;
+          }
+        } while (XCheckWindowEvent(dpy,_window,ButtonReleaseMask,&event));
+      } break;
+      case KeyPress : {
+        char tmp = 0; KeySym ksym;
+        XLookupString(&event.xkey,&tmp,1,&ksym,0);
+        set_key((unsigned int)ksym,true);
+      } break;
+      case KeyRelease : {
+        char tmp = 0; KeySym ksym;
+        XLookupString(&event.xkey,&tmp,1,&ksym,0);
+        set_key((unsigned int)ksym,false);
+      } break;
+      case EnterNotify: {
+        while (XCheckWindowEvent(dpy,_window,EnterWindowMask,&event)) {}
+        _mouse_x = event.xmotion.x;
+        _mouse_y = event.xmotion.y;
+        if (_mouse_x<0 || _mouse_y<0 || _mouse_x>=width() || _mouse_y>=height()) _mouse_x = _mouse_y = -1;
+      } break;
+      case LeaveNotify : {
+        while (XCheckWindowEvent(dpy,_window,LeaveWindowMask,&event)) {}
+        _mouse_x = _mouse_y =-1; _is_event = true;
+      } break;
+      case MotionNotify : {
+        while (XCheckWindowEvent(dpy,_window,PointerMotionMask,&event)) {}
+        _mouse_x = event.xmotion.x;
+        _mouse_y = event.xmotion.y;
+        if (_mouse_x<0 || _mouse_y<0 || _mouse_x>=width() || _mouse_y>=height()) _mouse_x = _mouse_y = -1;
+        _is_event = true;
+      } break;
+      }
+    }
+
+    static void* _events_thread(void *) { // Only one thread to handle events for all opened display windows.
+      Display *const dpy = cimg::X11_attr().display;
+      XEvent event;
+      pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED,0);
+      pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,0);
+      for (;;) {
+        XLockDisplay(dpy);
+        bool event_flag = XCheckTypedEvent(dpy,ClientMessage,&event);
+        if (!event_flag) event_flag = XCheckMaskEvent(dpy,
+                                                      ExposureMask | StructureNotifyMask | ButtonPressMask|
+                                                      KeyPressMask | PointerMotionMask | EnterWindowMask | LeaveWindowMask|
+                                                      ButtonReleaseMask | KeyReleaseMask,&event);
+        if (event_flag)
+          for (unsigned int i = 0; i<cimg::X11_attr().nb_wins; ++i)
+            if (!cimg::X11_attr().wins[i]->_is_closed && event.xany.window==cimg::X11_attr().wins[i]->_window)
+              cimg::X11_attr().wins[i]->_handle_events(&event);
+        XUnlockDisplay(dpy);
+        pthread_testcancel();
+        cimg::sleep(8);
+      }
+      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::X11_attr().display,colormap,palette,256);
+    }
+
+    void _map_window() {
+      Display *const dpy = cimg::X11_attr().display;
+      bool is_exposed = false, is_mapped = false;
+      XWindowAttributes attr;
+      XEvent event;
+      XMapRaised(dpy,_window);
+      do { // Wait for the window to be mapped.
+        XWindowEvent(dpy,_window,StructureNotifyMask | ExposureMask,&event);
+        switch (event.type) {
+        case MapNotify : is_mapped = true; break;
+        case Expose : is_exposed = true; break;
+        }
+      } while (!is_exposed || !is_mapped);
+      do { // Wait for the window to be visible.
+        XGetWindowAttributes(dpy,_window,&attr);
+        if (attr.map_state!=IsViewable) { XSync(dpy,0); 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 || !_image) return;
+      Display *const dpy = cimg::X11_attr().display;
+      if (wait_expose) { // Send an expose event sticked to display window to force repaint.
+        static XEvent event;
+        event.xexpose.type = Expose;
+        event.xexpose.serial = 0;
+        event.xexpose.send_event = 1;
+        event.xexpose.display = dpy;
+        event.xexpose.window = _window;
+        event.xexpose.x = 0;
+        event.xexpose.y = 0;
+        event.xexpose.width = width();
+        event.xexpose.height = height();
+        event.xexpose.count = 0;
+        XSendEvent(dpy,_window,0,0,&event);
+      } else { // Repaint directly (may be called from the expose event).
+        GC gc = DefaultGC(dpy,DefaultScreen(dpy));
+#ifdef cimg_use_xshm
+        if (_shminfo) {
+          const int completion_type = XShmGetEventBase(dpy) + ShmCompletion;
+          XEvent event;
+          XShmPutImage(dpy,_window,gc,_image,0,0,0,0,_width,_height,1);
+          do { XNextEvent(dpy,&event); } while (event.type!=completion_type);  // Wait for the image drawing to be completed.
+        } else XPutImage(dpy,_window,gc,_image,0,0,0,0,_width,_height);
+#else
+        XPutImage(dpy,_window,gc,_image,0,0,0,0,_width,_height);
+#endif
+      }
+    }
+
+    template<typename T>
+    void _resize(T pixel_type, const unsigned int ndimx, const unsigned int ndimy, const bool force_redraw) {
+      Display *const dpy = cimg::X11_attr().display;
+      cimg::unused(pixel_type);
+
+#ifdef cimg_use_xshm
+      if (_shminfo) {
+        XShmSegmentInfo *const nshminfo = new XShmSegmentInfo;
+        XImage *const nimage = XShmCreateImage(dpy,DefaultVisual(dpy,DefaultScreen(dpy)),
+                                               cimg::X11_attr().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 = 0;
+              cimg::X11_attr().is_shm_enabled = true;
+              XErrorHandler oldXErrorHandler = XSetErrorHandler(_assign_xshm);
+              XShmAttach(dpy,nshminfo);
+              XFlush(dpy);
+              XSetErrorHandler(oldXErrorHandler);
+              if (!cimg::X11_attr().is_shm_enabled) {
+                shmdt(nshminfo->shmaddr);
+                shmctl(nshminfo->shmid,IPC_RMID,0);
+                XDestroyImage(nimage);
+                delete nshminfo;
+                return;
+              } else {
+                T *const ndata = (T*)nimage->data;
+                if (force_redraw) _render_resize((T*)_data,_width,_height,ndata,ndimx,ndimy);
+                else std::memset(ndata,0,sizeof(T)*ndimx*ndimy);
+                XShmDetach(dpy,_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*)std::malloc(ndimx*ndimy*sizeof(T));
+          if (force_redraw) _render_resize((T*)_data,_width,_height,ndata,ndimx,ndimy);
+          else std::memset(ndata,0,sizeof(T)*ndimx*ndimy);
+          _data = (void*)ndata;
+          XDestroyImage(_image);
+          _image = XCreateImage(dpy,DefaultVisual(dpy,DefaultScreen(dpy)),
+                                cimg::X11_attr().nb_bits,ZPixmap,0,(char*)_data,ndimx,ndimy,8,0);
+        }
+    }
+
+    void _init_fullscreen() {
+      if (!_is_fullscreen || _is_closed) return;
+      Display *const dpy = cimg::X11_attr().display;
+      _background_window = 0;
+
+#ifdef cimg_use_xrandr
+      int foo;
+      if (XRRQueryExtension(dpy,&foo,&foo)) {
+        XRRRotations(dpy,DefaultScreen(dpy),&cimg::X11_attr().curr_rotation);
+        if (!cimg::X11_attr().resolutions) {
+          cimg::X11_attr().resolutions = XRRSizes(dpy,DefaultScreen(dpy),&foo);
+          cimg::X11_attr().nb_resolutions = (unsigned int)foo;
+        }
+        if (cimg::X11_attr().resolutions) {
+          cimg::X11_attr().curr_resolution = 0;
+          for (unsigned int i = 0; i<cimg::X11_attr().nb_resolutions; ++i) {
+            const unsigned int
+              nw = (unsigned int)(cimg::X11_attr().resolutions[i].width),
+              nh = (unsigned int)(cimg::X11_attr().resolutions[i].height);
+            if (nw>=_width && nh>=_height &&
+                nw<=(unsigned int)(cimg::X11_attr().resolutions[cimg::X11_attr().curr_resolution].width) &&
+                nh<=(unsigned int)(cimg::X11_attr().resolutions[cimg::X11_attr().curr_resolution].height))
+              cimg::X11_attr().curr_resolution = i;
+          }
+          if (cimg::X11_attr().curr_resolution>0) {
+            XRRScreenConfiguration *config = XRRGetScreenInfo(dpy,DefaultRootWindow(dpy));
+            XRRSetScreenConfig(dpy,config,DefaultRootWindow(dpy),
+                               cimg::X11_attr().curr_resolution,cimg::X11_attr().curr_rotation,CurrentTime);
+            XRRFreeScreenConfigInfo(config);
+            XSync(dpy,0);
+          }
+        }
+      }
+      if (!cimg::X11_attr().resolutions)
+        cimg::warn(_cimgdisplay_instance
+                   "init_fullscreen() : Xrandr extension not supported by the X server.",
+                   cimgdisplay_instance);
+#endif
+
+      const unsigned int sx = screen_width(), sy = screen_height();
+      if (sx==_width && sy==_height) return;
+      XSetWindowAttributes winattr;
+      winattr.override_redirect = 1;
+      _background_window = XCreateWindow(dpy,DefaultRootWindow(dpy),0,0,sx,sy,0,0,
+                                         InputOutput,CopyFromParent,CWOverrideRedirect,&winattr);
+      const unsigned int buf_size = sx*sy*(cimg::X11_attr().nb_bits==8?1:(cimg::X11_attr().nb_bits==16?2:4));
+      void *background_data = std::malloc(buf_size);
+      std::memset(background_data,0,buf_size);
+      XImage *background_image = XCreateImage(dpy,DefaultVisual(dpy,DefaultScreen(dpy)),cimg::X11_attr().nb_bits,
+                                              ZPixmap,0,(char*)background_data,sx,sy,8,0);
+      XEvent event;
+      XSelectInput(dpy,_background_window,StructureNotifyMask);
+      XMapRaised(dpy,_background_window);
+      do XWindowEvent(dpy,_background_window,StructureNotifyMask,&event);
+      while (event.type!=MapNotify);
+      GC gc = DefaultGC(dpy,DefaultScreen(dpy));
+#ifdef cimg_use_xshm
+      if (_shminfo) XShmPutImage(dpy,_background_window,gc,background_image,0,0,0,0,sx,sy,0);
+      else XPutImage(dpy,_background_window,gc,background_image,0,0,0,0,sx,sy);
+#else
+      XPutImage(dpy,_background_window,gc,background_image,0,0,0,0,sx,sy);
+#endif
+      XWindowAttributes attr;
+      XGetWindowAttributes(dpy,_background_window,&attr);
+      while (attr.map_state!=IsViewable) XSync(dpy,0);
+      XDestroyImage(background_image);
+    }
+
+    void _desinit_fullscreen() {
+      if (!_is_fullscreen) return;
+      Display *const dpy = cimg::X11_attr().display;
+      XUngrabKeyboard(dpy,CurrentTime);
+#ifdef cimg_use_xrandr
+      if (cimg::X11_attr().resolutions && cimg::X11_attr().curr_resolution) {
+        XRRScreenConfiguration *config = XRRGetScreenInfo(dpy,DefaultRootWindow(dpy));
+        XRRSetScreenConfig(dpy,config,DefaultRootWindow(dpy),0,cimg::X11_attr().curr_rotation,CurrentTime);
+        XRRFreeScreenConfigInfo(config);
+        XSync(dpy,0);
+        cimg::X11_attr().curr_resolution = 0;
+      }
+#endif
+      if (_background_window) XDestroyWindow(dpy,_background_window);
+      _background_window = 0;
+      _is_fullscreen = false;
+    }
+
+    static int _assign_xshm(Display *dpy, XErrorEvent *error) {
+      cimg::unused(dpy,error);
+      cimg::X11_attr().is_shm_enabled = false;
+      return 0;
+    }
+
+    void _assign(const unsigned int dimw, const unsigned int dimh, const char *const ptitle=0,
+                 const unsigned int normalization_type=3,
+                 const bool fullscreen_flag=false, const bool closed_flag=false) {
+
+      // Allocate space for window title
+      const char *const nptitle = ptitle?ptitle:"";
+      const unsigned int s = std::strlen(nptitle) + 1;
+      char *const tmp_title = s?new char[s]:0;
+      if (s) std::memcpy(tmp_title,nptitle,s*sizeof(char));
+
+      // Destroy previous display window if existing
+      if (!is_empty()) assign();
+
+      // Open X11 display and retrieve graphical properties.
+      Display* &dpy = cimg::X11_attr().display;
+      if (!dpy) {
+        static const int xinit_status = XInitThreads();
+        cimg::unused(xinit_status);
+        dpy = XOpenDisplay(0);
+        if (!dpy)
+          throw CImgDisplayException(_cimgdisplay_instance
+                                     "assign() : Failed to open X11 display.",
+                                     cimgdisplay_instance);
+
+        cimg::X11_attr().nb_bits = DefaultDepth(dpy,DefaultScreen(dpy));
+        if (cimg::X11_attr().nb_bits!=8 && cimg::X11_attr().nb_bits!=16 && cimg::X11_attr().nb_bits!=24 && cimg::X11_attr().nb_bits!=32)
+          throw CImgDisplayException(_cimgdisplay_instance
+                                     "assign() : Invalid %u bits screen mode detected "
+                                     "(only 8, 16, 24 and 32 bits modes are managed).",
+                                     cimgdisplay_instance,
+                                     cimg::X11_attr().nb_bits);
+        XVisualInfo vtemplate;
+        vtemplate.visualid = XVisualIDFromVisual(DefaultVisual(dpy,DefaultScreen(dpy)));
+        int nb_visuals;
+        XVisualInfo *vinfo = XGetVisualInfo(dpy,VisualIDMask,&vtemplate,&nb_visuals);
+        if (vinfo && vinfo->red_mask<vinfo->blue_mask) cimg::X11_attr().is_blue_first = true;
+        cimg::X11_attr().byte_order = ImageByteOrder(dpy);
+        XFree(vinfo);
+
+        XLockDisplay(dpy);
+        cimg::X11_attr().event_thread = new pthread_t;
+        pthread_create(cimg::X11_attr().event_thread,0,_events_thread,0);
+      } else XLockDisplay(dpy);
+
+      // Set display variables.
+      _width = cimg::min(dimw,(unsigned int)screen_width());
+      _height = cimg::min(dimh,(unsigned int)screen_height());
+      _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 LUT, if 8bits display)
+      if (_is_fullscreen) {
+        if (!_is_closed) _init_fullscreen();
+        const unsigned int sx = screen_width(), sy = screen_height();
+        XSetWindowAttributes winattr;
+        winattr.override_redirect = 1;
+        _window = XCreateWindow(dpy,DefaultRootWindow(dpy),(sx-_width)/2,(sy-_height)/2,_width,_height,0,0,
+                                InputOutput,CopyFromParent,CWOverrideRedirect,&winattr);
+      } else
+        _window = XCreateSimpleWindow(dpy,DefaultRootWindow(dpy),0,0,_width,_height,0,0L,0L);
+
+      XSelectInput(dpy,_window,
+                   ExposureMask | StructureNotifyMask | ButtonPressMask | KeyPressMask | PointerMotionMask |
+                   EnterWindowMask | LeaveWindowMask | ButtonReleaseMask | KeyReleaseMask);
+
+      XStoreName(dpy,_window,_title?_title:" ");
+      if (cimg::X11_attr().nb_bits==8) {
+        _colormap = XCreateColormap(dpy,_window,DefaultVisual(dpy,DefaultScreen(dpy)),AllocAll);
+        _set_colormap(_colormap,3);
+        XSetWindowColormap(dpy,_window,_colormap);
+      }
+
+      static const char *const _window_class = cimg_appname;
+      XClassHint *const window_class = XAllocClassHint();
+      window_class->res_name = (char*)_window_class;
+      window_class->res_class = (char*)_window_class;
+      XSetClassHint(dpy,_window,window_class);
+      XFree(window_class);
+
+      _window_width = _width;
+      _window_height = _height;
+
+      // Create XImage
+#ifdef cimg_use_xshm
+      _shminfo = 0;
+      if (XShmQueryExtension(dpy)) {
+        _shminfo = new XShmSegmentInfo;
+        _image = XShmCreateImage(dpy,DefaultVisual(dpy,DefaultScreen(dpy)),cimg::X11_attr().nb_bits,ZPixmap,0,_shminfo,_width,_height);
+        if (!_image) { delete _shminfo; _shminfo = 0; }
+        else {
+          _shminfo->shmid = shmget(IPC_PRIVATE,_image->bytes_per_line*_image->height,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 = 0;
+              cimg::X11_attr().is_shm_enabled = true;
+              XErrorHandler oldXErrorHandler = XSetErrorHandler(_assign_xshm);
+              XShmAttach(dpy,_shminfo);
+              XSync(dpy,0);
+              XSetErrorHandler(oldXErrorHandler);
+              if (!cimg::X11_attr().is_shm_enabled) {
+                shmdt(_shminfo->shmaddr); shmctl(_shminfo->shmid,IPC_RMID,0); XDestroyImage(_image); delete _shminfo; _shminfo = 0;
+              }
+            }
+          }
+        }
+      }
+      if (!_shminfo)
+#endif
+        {
+          const unsigned int buf_size = _width*_height*(cimg::X11_attr().nb_bits==8?1:(cimg::X11_attr().nb_bits==16?2:4));
+          _data = std::malloc(buf_size);
+          _image = XCreateImage(dpy,DefaultVisual(dpy,DefaultScreen(dpy)),cimg::X11_attr().nb_bits,ZPixmap,0,(char*)_data,_width,_height,8,0);
+        }
+
+      _wm_window_atom = XInternAtom(dpy,"WM_DELETE_WINDOW",0);
+      _wm_protocol_atom = XInternAtom(dpy,"WM_PROTOCOLS",0);
+      XSetWMProtocols(dpy,_window,&_wm_window_atom,1);
+
+      if (_is_fullscreen) XGrabKeyboard(dpy,_window,1,GrabModeAsync,GrabModeAsync,CurrentTime);
+      cimg::X11_attr().wins[cimg::X11_attr().nb_wins++]=this;
+      if (!_is_closed) _map_window(); else { _window_x = _window_y = cimg::type<int>::min(); }
+      XUnlockDisplay(dpy);
+    }
+
+    CImgDisplay& assign() {
+      if (is_empty()) return flush();
+      Display *const dpy = cimg::X11_attr().display;
+      XLockDisplay(dpy);
+
+      // Remove display window from event thread list.
+      unsigned int i;
+      for (i = 0; i<cimg::X11_attr().nb_wins && cimg::X11_attr().wins[i]!=this; ++i) {}
+      for (; i<cimg::X11_attr().nb_wins-1; ++i) cimg::X11_attr().wins[i] = cimg::X11_attr().wins[i+1];
+      --cimg::X11_attr().nb_wins;
+
+      // Destroy window, image, colormap and title.
+      if (_is_fullscreen && !_is_closed) _desinit_fullscreen();
+      XDestroyWindow(dpy,_window);
+      _window = 0;
+#ifdef cimg_use_xshm
+      if (_shminfo) {
+        XShmDetach(dpy,_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::X11_attr().nb_bits==8) XFreeColormap(dpy,_colormap);
+      _colormap = 0;
+      XSync(dpy,0);
+
+      // Reset display variables
+      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(dpy);
+      if (!cimg::X11_attr().nb_wins) {
+        // Kill event thread
+        //pthread_cancel(*cimg::X11_attr().event_thread);
+        //XUnlockDisplay(cimg::X11_attr().display);
+        //pthread_join(*cimg::X11_attr().event_thread,0);
+        //delete cimg::X11_attr().event_thread;
+        //cimg::X11_attr().event_thread = 0;
+        // XUnlockDisplay(cimg::X11_attr().display); // <- This call make the library hang sometimes (fix required).
+        // XCloseDisplay(cimg::X11_attr().display); // <- This call make the library hang sometimes (fix required).
+        //cimg::X11_attr().display = 0;
+      }
+      return *this;
+    }
+
+    CImgDisplay& assign(const unsigned int dimw, const unsigned int dimh, const char *const 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;
+      std::memset(_data,0,(cimg::X11_attr().nb_bits==8?sizeof(unsigned char):
+                          (cimg::X11_attr().nb_bits==16?sizeof(unsigned short):sizeof(unsigned int)))*_width*_height);
+      return paint();
+    }
+
+    template<typename T>
+    CImgDisplay& assign(const CImg<T>& img, const char *const 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.min_max(_max);
+      return render(nimg).paint();
+    }
+
+    template<typename T>
+    CImgDisplay& assign(const CImgList<T>& list, const char *const 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>'x', &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.min_max(_max);
+      return render(nimg).paint();
+    }
+
+    CImgDisplay& assign(const CImgDisplay& disp) {
+      if (!disp) return assign();
+      _assign(disp._width,disp._height,disp._title,disp._normalization,disp._is_fullscreen,disp._is_closed);
+      std::memcpy(_data,disp._data,(cimg::X11_attr().nb_bits==8?sizeof(unsigned char):
+                                    cimg::X11_attr().nb_bits==16?sizeof(unsigned short):
+                                    sizeof(unsigned int))*_width*_height);
+      return paint();
+    }
+
+    CImgDisplay& resize(const int nwidth, const int nheight, const bool force_redraw=true) {
+      if (!nwidth || !nheight || (is_empty() && (nwidth<0 || nheight<0))) return assign();
+      if (is_empty()) return assign(nwidth,nheight);
+      Display *const dpy = cimg::X11_attr().display;
+      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(dpy);
+      if (_window_width!=dimx || _window_height!=dimy) XResizeWindow(dpy,_window,dimx,dimy);
+      if (_width!=dimx || _height!=dimy) switch (cimg::X11_attr().nb_bits) {
+        case 8 :  { unsigned char pixel_type = 0; _resize(pixel_type,dimx,dimy,force_redraw); } break;
+        case 16 : { unsigned short pixel_type = 0; _resize(pixel_type,dimx,dimy,force_redraw); } break;
+        default : { unsigned int pixel_type = 0; _resize(pixel_type,dimx,dimy,force_redraw); }
+        }
+      _window_width = _width = dimx; _window_height = _height = dimy;
+      _is_resized = false;
+      XUnlockDisplay(dpy);
+      if (_is_fullscreen) move((screen_width()-_width)/2,(screen_height()-_height)/2);
+      if (force_redraw) return paint();
+      return *this;
+    }
+
+    CImgDisplay& toggle_fullscreen(const bool force_redraw=true) {
+      if (is_empty()) return *this;
+      if (force_redraw) {
+        const unsigned int buf_size = _width*_height*(cimg::X11_attr().nb_bits==8?1:(cimg::X11_attr().nb_bits==16?2:4));
+        void *image_data = std::malloc(buf_size);
+        std::memcpy(image_data,_data,buf_size);
+        assign(_width,_height,_title,_normalization,!_is_fullscreen,false);
+        std::memcpy(_data,image_data,buf_size);
+        std::free(image_data);
+        return paint();
+      }
+      return assign(_width,_height,_title,_normalization,!_is_fullscreen,false);
+    }
+
+    CImgDisplay& show() {
+      if (is_empty() || !_is_closed) return *this;
+      Display *const dpy = cimg::X11_attr().display;
+      XLockDisplay(dpy);
+      if (_is_fullscreen) _init_fullscreen();
+      _map_window();
+      _is_closed = false;
+      XUnlockDisplay(dpy);
+      return paint();
+    }
+
+    CImgDisplay& close() {
+      if (is_empty() || _is_closed) return *this;
+      Display *const dpy = cimg::X11_attr().display;
+      XLockDisplay(dpy);
+      if (_is_fullscreen) _desinit_fullscreen();
+      XUnmapWindow(dpy,_window);
+      _window_x = _window_y = -1;
+      _is_closed = true;
+      XUnlockDisplay(dpy);
+      return *this;
+    }
+
+    CImgDisplay& move(const int posx, const int posy) {
+      if (is_empty()) return *this;
+      Display *const dpy = cimg::X11_attr().display;
+      show();
+      XLockDisplay(dpy);
+      XMoveWindow(dpy,_window,posx,posy);
+      _window_x = posx; _window_y = posy;
+      _is_moved = false;
+      XUnlockDisplay(dpy);
+      return paint();
+    }
+
+    CImgDisplay& show_mouse() {
+      if (is_empty()) return *this;
+      Display *const dpy = cimg::X11_attr().display;
+      XLockDisplay(dpy);
+      XUndefineCursor(dpy,_window);
+      XUnlockDisplay(dpy);
+      return *this;
+    }
+
+    CImgDisplay& hide_mouse() {
+      if (is_empty()) return *this;
+      Display *const dpy = cimg::X11_attr().display;
+      XLockDisplay(dpy);
+      const char pix_data[8] = { 0 };
+      XColor col;
+      col.red = col.green = col.blue = 0;
+      Pixmap pix = XCreateBitmapFromData(dpy,_window,pix_data,8,8);
+      Cursor cur = XCreatePixmapCursor(dpy,pix,pix,&col,&col,0,0);
+      XFreePixmap(dpy,pix);
+      XDefineCursor(dpy,_window,cur);
+      XUnlockDisplay(dpy);
+      return *this;
+    }
+
+    CImgDisplay& set_mouse(const int posx, const int posy) {
+      if (is_empty() || _is_closed) return *this;
+      Display *const dpy = cimg::X11_attr().display;
+      XLockDisplay(dpy);
+      XWarpPointer(dpy,0L,_window,0,0,0,0,posx,posy);
+      _mouse_x = posx; _mouse_y = posy;
+      _is_moved = false;
+      XSync(dpy,0);
+      XUnlockDisplay(dpy);
+      return *this;
+    }
+
+    CImgDisplay& set_title(const char *const format, ...) {
+      if (is_empty()) return *this;
+      char tmp[1024] = { 0 };
+      va_list ap;
+      va_start(ap, format);
+      cimg_vsnprintf(tmp,sizeof(tmp),format,ap);
+      va_end(ap);
+      if (!std::strcmp(_title,tmp)) return *this;
+      delete[] _title;
+      const unsigned int s = std::strlen(tmp) + 1;
+      _title = new char[s];
+      std::memcpy(_title,tmp,s*sizeof(char));
+      Display *const dpy = cimg::X11_attr().display;
+      XLockDisplay(dpy);
+      XStoreName(dpy,_window,tmp);
+      XUnlockDisplay(dpy);
+      return *this;
+    }
+
+    template<typename T>
+    CImgDisplay& display(const CImg<T>& img) {
+      if (!img)
+        throw CImgArgumentException(_cimgdisplay_instance
+                                    "display() : Empty specified image.",
+                                    cimgdisplay_instance);
+      if (is_empty()) return assign(img);
+      return render(img).paint(false);
+    }
+
+    CImgDisplay& paint(const bool wait_expose=true) {
+      if (is_empty()) return *this;
+      Display *const dpy = cimg::X11_attr().display;
+      XLockDisplay(dpy);
+      _paint(wait_expose);
+      XUnlockDisplay(dpy);
+      return *this;
+    }
+
+    template<typename T>
+    CImgDisplay& render(const CImg<T>& img, const bool flag8=false) {
+      if (!img)
+        throw CImgArgumentException(_cimgdisplay_instance
+                                    "render() : Empty specified image.",
+                                    cimgdisplay_instance);
+      if (is_empty()) return *this;
+      if (img._depth!=1) return render(img.get_projections2d(img._width/2,img._height/2,img._depth/2));
+      if (cimg::X11_attr().nb_bits==8 && (img._width!=_width || img._height!=_height)) return render(img.get_resize(_width,_height,1,-100,1));
+      if (cimg::X11_attr().nb_bits==8 && !flag8 && img._spectrum==3) {
+        static const CImg<typename CImg<T>::ucharT> default_palette = CImg<typename CImg<T>::ucharT>::default_LUT256();
+        return render(img.get_index(default_palette,1,false));
+      }
+
+      Display *const dpy = cimg::X11_attr().display;
+      const T
+        *data1 = img._data,
+        *data2 = (img._spectrum>1)?img.data(0,0,0,1):data1,
+        *data3 = (img._spectrum>2)?img.data(0,0,0,2):data1;
+
+      if (cimg::X11_attr().is_blue_first) cimg::swap(data1,data3);
+      XLockDisplay(dpy);
+
+      if (!_normalization || (_normalization==3 && cimg::type<T>::string()==cimg::type<unsigned char>::string())) {
+        _min = _max = 0;
+        switch (cimg::X11_attr().nb_bits) {
+        case 8 : { // 256 color palette, no normalization
+          _set_colormap(_colormap,img._spectrum);
+          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._spectrum) {
+          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._spectrum) {
+          case 1 :
+            if (cimg::X11_attr().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::X11_attr().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::X11_attr().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._spectrum) {
+            case 1 :
+              if (cimg::X11_attr().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++);
+                  *(ptrd++) = (val<<16) | (val<<8) | val;
+                }
+              break;
+            case 2 :
+              if (cimg::X11_attr().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::X11_attr().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._spectrum) {
+            case 1 :
+              if (cimg::X11_attr().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::X11_attr().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::X11_attr().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.min_max(_max);
+          else { _min = (float)cimg::type<T>::min(); _max = (float)cimg::type<T>::max(); }
+        } else if ((_min>_max) || _normalization==1) _min = (float)img.min_max(_max);
+        const float delta = _max - _min, mm = 255/(delta?delta:1.0f);
+        switch (cimg::X11_attr().nb_bits) {
+        case 8 : { // 256 color palette, with normalization
+          _set_colormap(_colormap,img._spectrum);
+          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._spectrum) {
+          case 1 : for (unsigned int xy = img._width*img._height; xy>0; --xy) {
+              const unsigned char R = (unsigned char)((*(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)((*(data1++)-_min)*mm),
+                G = (unsigned char)((*(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)((*(data1++)-_min)*mm),
+                G = (unsigned char)((*(data2++)-_min)*mm),
+                B = (unsigned char)((*(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._spectrum) {
+          case 1 :
+            if (cimg::X11_attr().byte_order) for (unsigned int xy = img._width*img._height; xy>0; --xy) {
+              const unsigned char val = (unsigned char)((*(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)((*(data1++)-_min)*mm), G = val>>2;
+              *(ptrd++) = (G<<5) | (val>>3);
+              *(ptrd++) = (val&M) | (G>>3);
+            }
+            break;
+          case 2 :
+            if (cimg::X11_attr().byte_order) for (unsigned int xy = img._width*img._height; xy>0; --xy) {
+              const unsigned char G = (unsigned char)((*(data2++)-_min)*mm)>>2;
+              *(ptrd++) = ((unsigned char)((*(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)((*(data2++)-_min)*mm)>>2;
+              *(ptrd++) = (G<<5);
+              *(ptrd++) = ((unsigned char)((*(data1++)-_min)*mm)&M) | (G>>3);
+            }
+            break;
+          default :
+            if (cimg::X11_attr().byte_order) for (unsigned int xy = img._width*img._height; xy>0; --xy) {
+              const unsigned char G = (unsigned char)((*(data2++)-_min)*mm)>>2;
+              *(ptrd++) = ((unsigned char)((*(data1++)-_min)*mm)&M) | (G>>3);
+              *(ptrd++) = (G<<5) | ((unsigned char)((*(data3++)-_min)*mm)>>3);
+            } else for (unsigned int xy = img._width*img._height; xy>0; --xy) {
+              const unsigned char G = (unsigned char)((*(data2++)-_min)*mm)>>2;
+              *(ptrd++) = (G<<5) | ((unsigned char)((*(data3++)-_min)*mm)>>3);
+              *(ptrd++) = ((unsigned char)((*(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._spectrum) {
+            case 1 :
+              if (cimg::X11_attr().byte_order==cimg::endianness())
+                for (unsigned int xy = img._width*img._height; xy>0; --xy) {
+                  const unsigned char val = (unsigned char)((*(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)((*(data1++)-_min)*mm);
+                  *(ptrd++) = (val<<24) | (val<<16) | (val<<8);
+                }
+              break;
+            case 2 :
+              if (cimg::X11_attr().byte_order==cimg::endianness())
+                for (unsigned int xy = img._width*img._height; xy>0; --xy)
+                  *(ptrd++) =
+                    ((unsigned char)((*(data1++)-_min)*mm)<<16) |
+                    ((unsigned char)((*(data2++)-_min)*mm)<<8);
+              else
+                for (unsigned int xy = img._width*img._height; xy>0; --xy)
+                  *(ptrd++) =
+                    ((unsigned char)((*(data2++)-_min)*mm)<<16) |
+                    ((unsigned char)((*(data1++)-_min)*mm)<<8);
+              break;
+            default :
+              if (cimg::X11_attr().byte_order==cimg::endianness())
+                for (unsigned int xy = img._width*img._height; xy>0; --xy)
+                  *(ptrd++) =
+                    ((unsigned char)((*(data1++)-_min)*mm)<<16) |
+                    ((unsigned char)((*(data2++)-_min)*mm)<<8) |
+                    (unsigned char)((*(data3++)-_min)*mm);
+              else
+                for (unsigned int xy = img._width*img._height; xy>0; --xy)
+                  *(ptrd++) =
+                    ((unsigned char)((*(data3++)-_min)*mm)<<24) |
+                    ((unsigned char)((*(data2++)-_min)*mm)<<16) |
+                    ((unsigned char)((*(data1++)-_min)*mm)<<8);
+            }
+          } else {
+            unsigned char *ptrd = (unsigned char*)ndata;
+            switch (img._spectrum) {
+            case 1 :
+              if (cimg::X11_attr().byte_order) for (unsigned int xy = img._width*img._height; xy>0; --xy) {
+                const unsigned char val = (unsigned char)((*(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)((*(data1++)-_min)*mm);
+                (*ptrd++) = val;
+                (*ptrd++) = val;
+                (*ptrd++) = val;
+                (*ptrd++) = 0;
+              }
+              break;
+            case 2 :
+              if (cimg::X11_attr().byte_order) cimg::swap(data1,data2);
+              for (unsigned int xy = img._width*img._height; xy>0; --xy) {
+                (*ptrd++) = 0;
+                (*ptrd++) = (unsigned char)((*(data2++)-_min)*mm);
+                (*ptrd++) = (unsigned char)((*(data1++)-_min)*mm);
+                (*ptrd++) = 0;
+              }
+              break;
+            default :
+              if (cimg::X11_attr().byte_order) for (unsigned int xy = img._width*img._height; xy>0; --xy) {
+                (*ptrd++) = 0;
+                (*ptrd++) = (unsigned char)((*(data1++)-_min)*mm);
+                (*ptrd++) = (unsigned char)((*(data2++)-_min)*mm);
+                (*ptrd++) = (unsigned char)((*(data3++)-_min)*mm);
+              } else for (unsigned int xy = img._width*img._height; xy>0; --xy) {
+                (*ptrd++) = (unsigned char)((*(data3++)-_min)*mm);
+                (*ptrd++) = (unsigned char)((*(data2++)-_min)*mm);
+                (*ptrd++) = (unsigned char)((*(data1++)-_min)*mm);
+                (*ptrd++) = 0;
+              }
+            }
+          }
+          if (ndata!=_data) { _render_resize(ndata,img._width,img._height,(unsigned int*)_data,_width,_height); delete[] ndata; }
+        }
+        }
+      }
+      XUnlockDisplay(dpy);
+      return *this;
+    }
+
+    template<typename T>
+    const CImgDisplay& snapshot(CImg<T>& img) const {
+      if (is_empty()) { img.assign(); return *this; }
+      const unsigned char *ptrs = (unsigned char*)_data;
+      img.assign(_width,_height,1,3);
+      T
+        *data1 = img.data(0,0,0,0),
+        *data2 = img.data(0,0,0,1),
+        *data3 = img.data(0,0,0,2);
+      if (cimg::X11_attr().is_blue_first) cimg::swap(data1,data3);
+      switch (cimg::X11_attr().nb_bits) {
+      case 8 : {
+        for (unsigned int xy = img._width*img._height; xy>0; --xy) {
+          const unsigned char val = *(ptrs++);
+          *(data1++) = (T)(val&0xe0);
+          *(data2++) = (T)((val&0x1c)<<3);
+          *(data3++) = (T)(val<<6);
+        }
+      } break;
+      case 16 : {
+        if (cimg::X11_attr().byte_order) for (unsigned int xy = img._width*img._height; xy>0; --xy) {
+          const unsigned char val0 = *(ptrs++), val1 = *(ptrs++);
+          *(data1++) = (T)(val0&0xf8);
+          *(data2++) = (T)((val0<<5) | ((val1&0xe0)>>5));
+          *(data3++) = (T)(val1<<3);
+        } else for (unsigned int xy = img._width*img._height; xy>0; --xy) {
+          const unsigned short val0 = *(ptrs++), val1 = *(ptrs++);
+          *(data1++) = (T)(val1&0xf8);
+          *(data2++) = (T)((val1<<5) | ((val0&0xe0)>>5));
+          *(data3++) = (T)(val0<<3);
+        }
+      } break;
+      default : {
+        if (cimg::X11_attr().byte_order) for (unsigned int xy = img._width*img._height; xy>0; --xy) {
+          ++ptrs;
+          *(data1++) = (T)*(ptrs++);
+          *(data2++) = (T)*(ptrs++);
+          *(data3++) = (T)*(ptrs++);
+        } else for (unsigned int xy = img._width*img._height; xy>0; --xy) {
+            *(data3++) = (T)*(ptrs++);
+            *(data2++) = (T)*(ptrs++);
+            *(data1++) = (T)*(ptrs++);
+            ++ptrs;
+          }
+      }
+      }
+      return *this;
+    }
+
+    // Windows-based implementation.
+    //-------------------------------
+#elif cimg_display==2
+
+    bool _is_mouse_tracked, _is_cursor_visible;
+    HANDLE _thread, _is_created, _mutex;
+    HWND _window, _background_window;
+    CLIENTCREATESTRUCT _ccs;
+    unsigned int *_data;
+    DEVMODE _curr_mode;
+    BITMAPINFO _bmi;
+    HDC _hdc;
+
+    static int screen_width() {
+      DEVMODE mode;
+      mode.dmSize = sizeof(DEVMODE);
+      mode.dmDriverExtra = 0;
+      EnumDisplaySettings(0,ENUM_CURRENT_SETTINGS,&mode);
+      return mode.dmPelsWidth;
+    }
+
+    static int screen_height() {
+      DEVMODE mode;
+      mode.dmSize = sizeof(DEVMODE);
+      mode.dmDriverExtra = 0;
+      EnumDisplaySettings(0,ENUM_CURRENT_SETTINGS,&mode);
+      return mode.dmPelsHeight;
+    }
+
+    static void wait_all() {
+      WaitForSingleObject(cimg::Win32_attr().wait_event,INFINITE);
+    }
+
+    static LRESULT APIENTRY _handle_events(HWND window,UINT msg,WPARAM wParam,LPARAM lParam) {
+#ifdef _WIN64
+      CImgDisplay *const disp = (CImgDisplay*)GetWindowLongPtr(window,GWLP_USERDATA);
+#else
+      CImgDisplay *const 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;
+        disp->set_button().set_key(0).set_key(0,false)._is_closed = true;
+        ReleaseMutex(disp->_mutex);
+        ShowWindow(disp->_window,SW_HIDE);
+        disp->_is_event = true;
+        SetEvent(cimg::Win32_attr().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::Win32_attr().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::Win32_attr().wait_event);
+        }
+        ReleaseMutex(disp->_mutex);
+      } break;
+      case WM_PAINT :
+        disp->paint();
+        break;
+      case WM_KEYDOWN :
+        disp->set_key((unsigned int)wParam);
+        SetEvent(cimg::Win32_attr().wait_event);
+        break;
+      case WM_KEYUP :
+        disp->set_key((unsigned int)wParam,false);
+        SetEvent(cimg::Win32_attr().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->_is_mouse_tracked) {
+          TRACKMOUSEEVENT tme;
+          tme.cbSize = sizeof(TRACKMOUSEEVENT);
+          tme.dwFlags = TME_LEAVE;
+          tme.hwndTrack = disp->_window;
+          if (TrackMouseEvent(&tme)) disp->_is_mouse_tracked = true;
+        }
+#endif
+        if (disp->_mouse_x<0 || disp->_mouse_y<0 || disp->_mouse_x>=disp->width() || disp->_mouse_y>=disp->height())
+          disp->_mouse_x = disp->_mouse_y = -1;
+        disp->_is_event = true;
+        SetEvent(cimg::Win32_attr().wait_event);
+      } break;
+      case WM_MOUSELEAVE : {
+        disp->_mouse_x = disp->_mouse_y = -1;
+        disp->_is_mouse_tracked = false;
+      } break;
+      case WM_LBUTTONDOWN :
+        disp->set_button(1);
+        SetEvent(cimg::Win32_attr().wait_event);
+        break;
+      case WM_RBUTTONDOWN :
+        disp->set_button(2);
+        SetEvent(cimg::Win32_attr().wait_event);
+        break;
+      case WM_MBUTTONDOWN :
+        disp->set_button(3);
+        SetEvent(cimg::Win32_attr().wait_event);
+        break;
+      case WM_LBUTTONUP :
+        disp->set_button(1,false);
+        SetEvent(cimg::Win32_attr().wait_event);
+        break;
+      case WM_RBUTTONUP :
+        disp->set_button(2,false);
+        SetEvent(cimg::Win32_attr().wait_event);
+        break;
+      case WM_MBUTTONUP :
+        disp->set_button(3,false);
+        SetEvent(cimg::Win32_attr().wait_event);
+        break;
+      case 0x020A : // WM_MOUSEWHEEL:
+        disp->set_wheel((int)((short)HIWORD(wParam))/120);
+        SetEvent(cimg::Win32_attr().wait_event);
+      case WM_SETCURSOR :
+        if (disp->_is_cursor_visible) ShowCursor(TRUE);
+        else ShowCursor(FALSE);
+        break;
+      }
+      return DefWindowProc(window,msg,wParam,lParam);
+    }
+
+    static DWORD WINAPI _events_thread(void* arg) {
+      CImgDisplay *const disp = (CImgDisplay*)(((void**)arg)[0]);
+      const char *const 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 = -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_width(), sy = screen_height();
+        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->_is_created);
+      while (GetMessage(&msg,0,0,0)) DispatchMessage(&msg);
+      return 0;
+    }
+
+    CImgDisplay& _update_window_pos() {
+      if (_is_closed) _window_x = _window_y = -1;
+      else {
+        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;
+      }
+      return *this;
+    }
+
+    void _init_fullscreen() {
+      _background_window = 0;
+      if (!_is_fullscreen || _is_closed) _curr_mode.dmSize = 0;
+      else {
+        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_width(), sy = screen_height();
+        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);
+        }
+      }
+    }
+
+    void _desinit_fullscreen() {
+      if (!_is_fullscreen) return;
+      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 *const ptitle=0,
+                         const unsigned int normalization_type=3,
+                         const bool fullscreen_flag=false, const bool closed_flag=false) {
+
+      // Allocate space for window title
+      const char *const nptitle = ptitle?ptitle:"";
+      const unsigned int s = std::strlen(nptitle) + 1;
+      char *const tmp_title = s?new char[s]:0;
+      if (s) std::memcpy(tmp_title,nptitle,s*sizeof(char));
+
+      // Destroy previous window if existing
+      if (!is_empty()) assign();
+
+      // Set display variables
+      _width = cimg::min(dimw,(unsigned int)screen_width());
+      _height = cimg::min(dimh,(unsigned int)screen_height());
+      _normalization = normalization_type<4?normalization_type:3;
+      _is_fullscreen = fullscreen_flag;
+      _window_x = _window_y = 0;
+      _is_closed = closed_flag;
+      _is_cursor_visible = true;
+      _is_mouse_tracked = false;
+      _title = tmp_title;
+      flush();
+      if (_is_fullscreen) _init_fullscreen();
+
+      // Create event thread
+      void *const arg = (void*)(new void*[2]);
+      ((void**)arg)[0] = (void*)this;
+      ((void**)arg)[1] = (void*)_title;
+      unsigned long ThreadID = 0;
+      _mutex = CreateMutex(0,FALSE,0);
+      _is_created = CreateEvent(0,FALSE,FALSE,0);
+      _thread = CreateThread(0,0,_events_thread,arg,0,&ThreadID);
+      WaitForSingleObject(_is_created,INFINITE);
+      return *this;
+    }
+
+    CImgDisplay& assign() {
+      if (is_empty()) return flush();
+      DestroyWindow(_window);
+      TerminateThread(_thread,0);
+      delete[] _data;
+      delete[] _title;
+      _data = 0;
+      _title = 0;
+      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 *const 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;
+      std::memset(_data,0,sizeof(unsigned int)*_width*_height);
+      return paint();
+    }
+
+    template<typename T>
+    CImgDisplay& assign(const CImg<T>& img, const char *const 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.min_max(_max);
+      return display(nimg);
+    }
+
+    template<typename T>
+    CImgDisplay& assign(const CImgList<T>& list, const char *const 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>'x', &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.min_max(_max);
+      return display(nimg);
+    }
+
+    CImgDisplay& assign(const CImgDisplay& disp) {
+      if (!disp) return assign();
+      _assign(disp._width,disp._height,disp._title,disp._normalization,disp._is_fullscreen,disp._is_closed);
+      std::memcpy(_data,disp._data,sizeof(unsigned int)*_width*_height);
+      return paint();
+    }
+
+    CImgDisplay& resize(const int nwidth, const int nheight, const bool force_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 *const ndata = new unsigned int[dimx*dimy];
+        if (force_redraw) _render_resize(_data,_width,_height,ndata,dimx,dimy);
+        else 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_width()-_width)/2,(screen_height()-_height)/2);
+      if (force_redraw) return paint();
+      return *this;
+    }
+
+    CImgDisplay& toggle_fullscreen(const bool force_redraw=true) {
+      if (is_empty()) return *this;
+      if (force_redraw) {
+        const unsigned int buf_size = _width*_height*4;
+        void *odata = std::malloc(buf_size);
+        std::memcpy(odata,_data,buf_size);
+        assign(_width,_height,_title,_normalization,!_is_fullscreen,false);
+        std::memcpy(_data,odata,buf_size);
+        std::free(odata);
+        return paint();
+      }
+      return assign(_width,_height,_title,_normalization,!_is_fullscreen,false);
+    }
+
+    CImgDisplay& show() {
+      if (is_empty() || !_is_closed) return *this;
+      _is_closed = false;
+      if (_is_fullscreen) _init_fullscreen();
+      ShowWindow(_window,SW_SHOW);
+      _update_window_pos();
+      return paint();
+    }
+
+    CImgDisplay& close() {
+      if (is_empty() || _is_closed) return *this;
+      _is_closed = true;
+      if (_is_fullscreen) _desinit_fullscreen();
+      ShowWindow(_window,SW_HIDE);
+      _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;
+      _is_cursor_visible = true;
+      ShowCursor(TRUE);
+      SendMessage(_window,WM_SETCURSOR,0,0);
+      return *this;
+    }
+
+    CImgDisplay& hide_mouse() {
+      if (is_empty()) return *this;
+      _is_cursor_visible = 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) return *this;
+      _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 *const format, ...) {
+      if (is_empty()) return *this;
+      char tmp[1024] = { 0 };
+      va_list ap;
+      va_start(ap, format);
+      cimg_vsnprintf(tmp,sizeof(tmp),format,ap);
+      va_end(ap);
+      if (!std::strcmp(_title,tmp)) return *this;
+      delete[] _title;
+      const unsigned int s = std::strlen(tmp) + 1;
+      _title = new char[s];
+      std::memcpy(_title,tmp,s*sizeof(char));
+      SetWindowTextA(_window, tmp);
+      return *this;
+    }
+
+    template<typename T>
+    CImgDisplay& display(const CImg<T>& img) {
+      if (!img)
+        throw CImgArgumentException(_cimgdisplay_instance
+                                    "display() : Empty specified image.",
+                                    cimgdisplay_instance);
+      if (is_empty()) return assign(img);
+      return render(img).paint();
+    }
+
+    CImgDisplay& paint() {
+      if (_is_closed) return *this;
+      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 (!img)
+        throw CImgArgumentException(_cimgdisplay_instance
+                                    "render() : Empty specified image.",
+                                    cimgdisplay_instance);
+
+      if (is_empty()) return *this;
+      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._spectrum>=2)?img.data(0,0,0,1):data1,
+        *data3 = (img._spectrum>=3)?img.data(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._spectrum) {
+        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.min_max(_max);
+          else { _min = (float)cimg::type<T>::min(); _max = (float)cimg::type<T>::max(); }
+        } else if ((_min>_max) || _normalization==1) _min = (float)img.min_max(_max);
+        const float delta = _max - _min, mm = 255/(delta?delta:1.0f);
+        switch (img._spectrum) {
+        case 1 : {
+          for (unsigned int xy = img._width*img._height; xy>0; --xy) {
+            const unsigned char val = (unsigned char)((*(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)((*(data1++)-_min)*mm),
+              G = (unsigned char)((*(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)((*(data1++)-_min)*mm),
+              G = (unsigned char)((*(data2++)-_min)*mm),
+              B = (unsigned char)((*(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(); return *this; }
+      const unsigned int *ptrs = _data;
+      img.assign(_width,_height,1,3);
+      T
+        *data1 = img.data(0,0,0,0),
+        *data2 = img.data(0,0,0,1),
+        *data3 = img.data(0,0,0,2);
+      for (unsigned int xy = img._width*img._height; xy>0; --xy) {
+        const unsigned int val = *(ptrs++);
+        *(data1++) = (T)(unsigned char)(val>>16);
+        *(data2++) = (T)(unsigned char)((val>>8)&0xFF);
+        *(data3++) = (T)(unsigned char)(val&0xFF);
+      }
+      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 \c CImg<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 \c CImgList<T> rather than simple images \c CImg<T>.
+
+     Thus, the \c CImg<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 \c CImg<T> structure contains \e six fields :
+     - \c _width defines the number of \a columns of the image (size along the X-axis).
+     - \c _height defines the number of \a rows of the image (size along the Y-axis).
+     - \c _depth defines the number of \a slices of the image (size along the Z-axis).
+     - \c _spectrum defines the number of \a channels of the image (size along the C-axis).
+     - \c _data defines a \a pointer to the \a pixel \a data (of type \c T).
+     - \c _is_shared is a boolean that tells if the memory buffer \c data is shared with
+       another image.
+
+     You can access these fields publicly although it is recommended to use the dedicated functions
+     width(), height(), depth(), spectrum() 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 \c 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 \c CImg<T> class contains a lot of functions that operates on images.
+     Some of the most useful are :
+
+     - operator()() : allows to access or write pixel values.
+     - display() : displays the image in a new window.
+  **/
+  template<typename T>
+  struct CImg {
+
+    unsigned int _width, _height, _depth, _spectrum;
+    bool _is_shared;
+    T *_data;
+
+    //! Simple iterator type, to loop through each pixel value of an image instance.
+    /**
+       \note
+       - The \c CImg<T>::iterator type is defined to be a <tt>T*</tt>.
+       - You will seldom have to use iterators in %CImg, most classical operations
+         being achieved (often in a faster way) using methods of \c CImg<T>.
+       \par Example
+       \code
+       CImg<float> img("reference.jpg");                                         // Load image from file.
+       for (CImg<float>::iterator it = img.begin(), it<img.end(); ++it) *it = 0; // Set all pixels to '0', through a CImg iterator.
+       img.fill(0);                                                              // Do the same with a built-in method.
+       \endcode
+       \see const_iterator.
+   **/
+    typedef T* iterator;
+
+    //! Simple const iterator type, to loop through each pixel value of a \c const image instance.
+    /**
+       \note
+       - The \c CImg<T>::const_iterator type is defined to be a \c const \c T*.
+       - You will seldom have to use iterators in %CImg, most classical operations
+         being achieved (often in a faster way) using methods of \c CImg<T>.
+       \par Example
+       \code
+       const CImg<float> img("reference.jpg");                                    // Load image from file.
+       float sum = 0;
+       for (CImg<float>::iterator it = img.begin(), it<img.end(); ++it) sum+=*it; // Compute sum of all pixel values, through a CImg iterator.
+       const float sum2 = img.sum();                                              // Do the same with a built-in method.
+       \endcode
+       \see iterator.
+    **/
+    typedef const T* const_iterator;
+
+    //! Pixel value type.
+    /**
+       Refer to the type of the pixel values of an image instance.
+       \note
+       - The \c CImg<T>::value_type type of a \c CImg<T> is defined to be a \c T.
+       - \c CImg<T>::value_type is actually not used in %CImg methods. It has been mainly defined for
+         compatibility with STL naming conventions.
+    **/
+    typedef T value_type;
+
+    // Define common types related to template type T.
+    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
+
+    //@}
+    //---------------------------------------------------------
+    //
+    //! \name Constructors / Destructor / Instance Management
+    //@{
+    //---------------------------------------------------------
+
+    //! Destructor.
+    /**
+       Destroy current image instance.
+       \note
+       - The pixel buffer data() is deallocated if necessary, e.g. for non-empty and non-shared image instances.
+       - Destroying an empty or shared image does nothing actually.
+       \warning
+       - When destroying a non-shared image, make sure that you will \e not operate on a remaining shared image
+         that shares its buffer with the destroyed instance, in order to avoid further invalid memory access (to a deallocated buffer).
+       \see CImg(),
+           assign().
+    **/
+    ~CImg() {
+      if (!_is_shared) delete[] _data;
+    }
+
+    //! Default constructor.
+    /**
+       Construct a new empty image instance.
+       \note
+       - An empty image has no pixel data and all of its dimensions width(), height(), depth(), spectrum()
+         are set to \c 0, as well as its pixel buffer pointer data().
+       - An empty image may be re-assigned afterwards, e.g. with the family of assign(unsigned int,unsigned int,unsigned int,unsigned int) methods,
+         or by operator=(const CImg<t>&). In all cases, the type of pixels stays \c T.
+       - An empty image is never shared.
+       \par Example
+       \code
+       CImg<float> img1, img2;      // Construct two empty images.
+       img1.assign(256,256,1,3);    // Re-assign 'img1' to be a 256x256x1x3 (color) image.
+       img2 = img1.get_rand(0,255); // Re-assign 'img2' to be a random-valued version of 'img1'.
+       img2.assign();               // Re-assign 'img2' to be an empty image again.
+       \endcode
+       \see ~CImg(),
+           assign(),
+           is_empty().
+    **/
+    CImg():_width(0),_height(0),_depth(0),_spectrum(0),_is_shared(false),_data(0) {}
+
+    //! Construct image with specified size.
+    /**
+       Construct a new image instance of size \c size_x x \c size_y x \c size_z x \c size_c, with pixels of type \c T.
+       \param size_x : Desired image width().
+       \param size_y : Desired image height().
+       \param size_z : Desired image depth().
+       \param size_c : Desired image spectrum().
+       \note
+       - It is able to create only \e non-shared images, and allocates thus a pixel buffer data() for each constructed image instance.
+       - Setting one dimension \c size_x,\c size_y,\c size_z or \c size_c to \c 0 leads to the construction of an \e empty image.
+       - A \c CImgInstanceException is thrown when the pixel buffer cannot be allocated (e.g. when requested size is too big for available memory).
+       \warning
+       - The allocated pixel buffer is \e not filled with a default value, and is likely to contain garbage values.
+         In order to initialize pixel values during construction (e.g. with \c 0), use constructor
+         CImg(unsigned int,unsigned int,unsigned int,unsigned int,T) instead.
+       \par Example
+       \code
+       CImg<float> img1(256,256,1,3);   // Construct a 256x256x1x3 (color) image, filled with garbage values.
+       CImg<float> img2(256,256,1,3,0); // Construct a 256x256x1x3 (color) image, filled with value '0'.
+       \endcode
+       \see CImg(unsigned int,unsigned int,unsigned int,unsigned int,T),
+           assign(unsigned int,unsigned int,unsigned int,unsigned int).
+    **/
+    explicit CImg(const unsigned int size_x, const unsigned int size_y=1, const unsigned int size_z=1, const unsigned int size_c=1):
+      _is_shared(false) {
+      const unsigned int siz = size_x*size_y*size_z*size_c;
+      if (siz) {
+        _width = size_x; _height = size_y; _depth = size_z; _spectrum = size_c;
+        try { _data = new T[siz]; } catch (...) {
+          _width = _height = _depth = _spectrum = 0; _data = 0;
+          throw CImgInstanceException(_cimg_instance
+                                      "CImg() : Failed to allocate memory (%s) for image (%u,%u,%u,%u).",
+                                      cimg_instance,
+                                      cimg::strbuffersize(size_x*size_y*size_z*size_c*sizeof(T)),size_x,size_y,size_z,size_c);
+        }
+      } else { _width = _height = _depth = _spectrum = 0; _data = 0; }
+    }
+
+    //! Construct image with specified size and initialize pixel values.
+    /**
+       Construct a new image instance of size \c size_x x \c size_y x \c size_z x \c size_c, with pixels of type \c T, and set all pixel
+       values to specified \c value.
+       \param size_x : Desired image width().
+       \param size_y : Desired image height().
+       \param size_z : Desired image depth().
+       \param size_c : Desired image spectrum().
+       \param value : Value used for initialization.
+       \note
+       - Similar to CImg(unsigned int,unsigned int,unsigned int,unsigned int),
+         but it also fills the pixel buffer with the specified \c value.
+       \warning
+       - It cannot be used to construct a vector-valued image and initialize it with \e vector-valued pixels (e.g. RGB vector, for color images).
+         For this task, you may use fillC() after construction.
+       \see CImg(unsigned int,unsigned int,unsigned int,unsigned int),
+           assign(unsigned int,unsigned int,unsigned int,unsigned int,T).
+    **/
+    CImg(const unsigned int size_x, const unsigned int size_y, const unsigned int size_z, const unsigned int size_c, const T value):
+      _is_shared(false) {
+      const unsigned int siz = size_x*size_y*size_z*size_c;
+      if (siz) {
+        _width = size_x; _height = size_y; _depth = size_z; _spectrum = size_c;
+        try { _data = new T[siz]; } catch (...) {
+          _width = _height = _depth = _spectrum = 0; _data = 0;
+          throw CImgInstanceException(_cimg_instance
+                                      "CImg() : Failed to allocate memory (%s) for image (%u,%u,%u,%u).",
+                                      cimg_instance,
+                                      cimg::strbuffersize(size_x*size_y*size_z*size_c*sizeof(T)),size_x,size_y,size_z,size_c);
+        }
+        fill(value);
+      } else { _width = _height = _depth = _spectrum = 0; _data = 0; }
+    }
+
+    //! Construct image with specified size and initialize pixel values from a sequence of integers.
+    /**
+       Construct a new image instance of size \c size_x x \c size_y x \c size_z x \c size_c, with pixels of type \c T, and initialize pixel
+       values from the specified sequence of integers \c value0,\c value1,\c ...
+       \param size_x : Desired image width().
+       \param size_y : Desired image height().
+       \param size_z : Desired image depth().
+       \param size_c : Desired image spectrum().
+       \param value0 : First value of the initialization sequence (must be an \e integer).
+       \param value1 : Second value of the initialization sequence (must be an \e integer).
+       \param ...
+       \note
+       - Similar to CImg(unsigned int,unsigned int,unsigned int,unsigned int), but it also fills
+         the pixel buffer with a sequence of specified integer values.
+       \warning
+       - You must specify \e exactly \c size_x*\c size_y*\c size_z*\c size_c integers in the initialization sequence.
+         Otherwise, the constructor may crash or fill your image pixels with garbage.
+       \par Example
+       \code
+       const CImg<float> img(2,2,1,3,      // Construct a 2x2 color (RGB) image.
+                             0,255,0,255,  // Set the 4 values for the red component.
+                             0,0,255,255,  // Set the 4 values for the green component.
+                             64,64,64,64); // Set the 4 values for the blue component.
+       img.resize(150,150).display();
+       \endcode
+       \image html ref_constructor1.jpg
+       \see CImg(unsigned int,unsigned int,unsigned int,unsigned int),
+           CImg(unsigned int,unsigned int,unsigned int,unsigned int,double,double,...),
+           assign(unsigned int,unsigned int,unsigned int,unsigned int,int,int,...).
+     **/
+    CImg(const unsigned int size_x, const unsigned int size_y, const unsigned int size_z, const unsigned int size_c,
+         const int value0, const int value1, ...):_width(0),_height(0),_depth(0),_spectrum(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(size_x,size_y,size_z,size_c);
+      _CImg_stdarg(*this,value0,value1,size_x*size_y*size_z*size_c,int);
+    }
+
+    //! Construct image with specified size and initialize pixel values from a sequence of doubles.
+    /**
+       Construct a new image instance of size \c size_x x \c size_y x \c size_z x \c size_c, with pixels of type \c T, and initialize pixel
+       values from the specified sequence of doubles \c value0,\c value1,\c ...
+       \param size_x : Desired image width().
+       \param size_y : Desired image height().
+       \param size_z : Desired image depth().
+       \param size_c : Desired image spectrum().
+       \param value0 : First value of the initialization sequence (must be a \e double).
+       \param value1 : Second value of the initialization sequence (must be a \e double).
+       \param ...
+       \note
+       - Similar to CImg(unsigned int,unsigned int,unsigned int,unsigned int,int,int,...), but
+         takes a sequence of double values instead of integers.
+       \warning
+       - You must specify \e exactly \c dx*\c dy*\c dz*\c dc doubles in the initialization sequence.
+         Otherwise, the constructor may crash or fill your image with garbage.
+         For instance, the code below will probably crash on most platforms :
+         \code
+         const CImg<float> img(2,2,1,1, 0.5,0.5,255,255); // FAIL : The two last arguments are 'int', not 'double' !
+         \endcode
+       \see CImg(unsigned int,unsigned int,unsigned int,unsigned int),
+           CImg(unsigned int,unsigned int,unsigned int,unsigned int,int,int,...),
+           assign(unsigned int,unsigned int,unsigned int,unsigned int,double,double,...).
+     **/
+    CImg(const unsigned int size_x, const unsigned int size_y, const unsigned int size_z, const unsigned int size_c,
+         const double value0, const double value1, ...):_width(0),_height(0),_depth(0),_spectrum(0),_is_shared(false),_data(0) {
+      assign(size_x,size_y,size_z,size_c);
+      _CImg_stdarg(*this,value0,value1,size_x*size_y*size_z*size_c,double);
+    }
+
+    //! Construct image with specified size and initialize pixel values from a value string.
+    /**
+       Construct a new image instance of size \c size_x x \c size_y x \c size_z x \c size_c, with pixels of type \c T, and initializes pixel
+       values from the specified string \c values.
+       \param size_x : Desired image width().
+       \param size_y : Desired image height().
+       \param size_z : Desired image depth().
+       \param size_c : Desired image spectrum().
+       \param values : Value string describing the way pixel values are set.
+       \param repeat_values : Flag telling if the value filling process is periodic.
+       \note
+       - Similar to CImg(unsigned int,unsigned int,unsigned int,unsigned int), but it also fills
+         the pixel buffer with values described in the value string \c values.
+       - Value string \c values may describe two different filling processes :
+         - Either \c values is a sequences of values assigned to the image pixels, as in <tt>"1,2,3,7,8,2"</tt>.
+           In this case, set \c repeat_values to \c true to periodically fill the image with the value sequence.
+         - Either, \c values is a formula, as in <tt>"cos(x/10)*sin(y/20)"</tt>. In this case, parameter \c repeat_values is pointless.
+       - For both cases, specifying \c repeat_values is mandatory. It disambiguates the possible overloading of constructor
+         CImg(unsigned int,unsigned int,unsigned int,unsigned int,T) with \c T being a <tt>const char*</tt>.
+       - A \c CImgArgumentException is thrown when an invalid value string \c values is specified.
+       \par Example
+       \code
+       const CImg<float> img1(129,129,1,3,"0,64,128,192,255",true),                   // Construct image filled from a value sequence.
+                         img2(129,129,1,3,"if(c==0,255*abs(cos(x/10)),1.8*y)",false); // Construct image filled from a formula.
+       (img1,img2).display();
+       \endcode
+       \image html ref_constructor2.jpg
+       \see CImg(unsigned int,unsigned int,unsigned int,unsigned int),
+           assign(unsigned int,unsigned int,unsigned int,unsigned int,const char*,bool).
+     **/
+    CImg(const unsigned int size_x, const unsigned int size_y, const unsigned int size_z, const unsigned int size_c,
+         const char *const values, const bool repeat_values):_is_shared(false) {
+      const unsigned int siz = size_x*size_y*size_z*size_c;
+      if (siz) {
+        _width = size_x; _height = size_y; _depth = size_z; _spectrum = size_c;
+        try { _data = new T[siz]; } catch (...) {
+          _width = _height = _depth = _spectrum = 0; _data = 0;
+          throw CImgInstanceException(_cimg_instance
+                                      "CImg() : Failed to allocate memory (%s) for image (%u,%u,%u,%u).",
+                                      cimg_instance,
+                                      cimg::strbuffersize(size_x*size_y*size_z*size_c*sizeof(T)),size_x,size_y,size_z,size_c);
+        }
+        fill(values,repeat_values);
+      } else { _width = _height = _depth = _spectrum = 0; _data = 0; }
+    }
+
+    //! Construct image with specified size and initialize pixel values from a memory buffer.
+    /**
+       Construct a new image instance of size \c size_x x \c size_y x \c size_z x \c size_c, with pixels of type \c T, and initializes pixel
+       values from the specified \c t* memory buffer.
+       \param values : Pointer to the input memory buffer.
+       \param size_x : Desired image width().
+       \param size_y : Desired image height().
+       \param size_z : Desired image depth().
+       \param size_c : Desired image spectrum().
+       \param is_shared : Flag telling if input memory buffer must be shared by the current instance.
+       \note
+       - If \c is_shared is \c false, the image instance allocates its own pixel buffer, and values from the specified input buffer
+         are copied to the instance buffer. If buffer types \c T and \c t are different, a regular static cast is performed during buffer copy.
+       - Otherwise, the image instance does \e not allocate a new buffer, and uses the input memory buffer as its own pixel buffer. This case
+         requires that types \c T and \c t are the same. Later, destroying such a shared image will not deallocate the pixel buffer,
+         this task being obviously charged to the initial buffer allocator.
+       - A \c CImgInstanceException is thrown when the pixel buffer cannot be allocated (e.g. when requested size is too big for available memory).
+       \warning
+       - You must take care when operating on a shared image, since it may have an invalid pixel buffer pointer data() (e.g. already deallocated).
+       \par Example
+       \code
+       unsigned char tab[256*256] = { 0 };
+       CImg<unsigned char> img1(tab,256,256,1,1,false), // Construct new non-shared image from buffer 'tab'.
+                           img2(tab,256,256,1,1,true);  // Construct new shared-image from buffer 'tab'.
+       tab[1024] = 255;                                 // Here, 'img2' is indirectly modified, but not 'img1'.
+       \endcode
+       \see CImg(unsigned int,unsigned int,unsigned int,unsigned int),
+           assign(const t*,unsigned int,unsigned int,unsigned int,unsigned int,bool),
+           is_shared().
+    **/
+    template<typename t>
+    CImg(const t *const values, const unsigned int size_x, const unsigned int size_y=1,
+         const unsigned int size_z=1, const unsigned int size_c=1, const bool is_shared=false):_is_shared(false) {
+      if (is_shared) {
+        _width = _height = _depth = _spectrum = 0; _data = 0;
+        throw CImgArgumentException(_cimg_instance
+                                    "CImg() : Invalid construction request of a (%u,%u,%u,%u) shared instance from a (%s*) buffer "
+                                    "(pixel types are different).",
+                                    cimg_instance,
+                                    size_x,size_y,size_z,size_c,CImg<t>::pixel_type());
+      }
+      const unsigned int siz = size_x*size_y*size_z*size_c;
+      if (values && siz) {
+        _width = size_x; _height = size_y; _depth = size_z; _spectrum = size_c;
+        try { _data = new T[siz]; } catch (...) {
+          _width = _height = _depth = _spectrum = 0; _data = 0;
+          throw CImgInstanceException(_cimg_instance
+                                      "CImg() : Failed to allocate memory (%s) for image (%u,%u,%u,%u).",
+                                      cimg_instance,
+                                      cimg::strbuffersize(size_x*size_y*size_z*size_c*sizeof(T)),size_x,size_y,size_z,size_c);
+
+        }
+        const t *ptrs = values + siz; cimg_for(*this,ptrd,T) *ptrd = (T)*(--ptrs);
+      } else { _width = _height = _depth = _spectrum = 0; _data = 0; }
+    }
+
+    //! Construct image with specified size and initialize pixel values from a memory buffer \specialization.
+    CImg(const T *const values, const unsigned int size_x, const unsigned int size_y=1,
+         const unsigned int size_z=1, const unsigned int size_c=1, const bool is_shared=false) {
+      const unsigned int siz = size_x*size_y*size_z*size_c;
+      if (values && siz) {
+        _width = size_x; _height = size_y; _depth = size_z; _spectrum = size_c; _is_shared = is_shared;
+        if (_is_shared) _data = const_cast<T*>(values);
+        else {
+          try { _data = new T[siz]; } catch (...) {
+            _width = _height = _depth = _spectrum = 0; _data = 0;
+            throw CImgInstanceException(_cimg_instance
+                                        "CImg() : Failed to allocate memory (%s) for image (%u,%u,%u,%u).",
+                                        cimg_instance,
+                                        cimg::strbuffersize(size_x*size_y*size_z*size_c*sizeof(T)),size_x,size_y,size_z,size_c);
+          }
+          std::memcpy(_data,values,siz*sizeof(T)); }
+      } else { _width = _height = _depth = _spectrum = 0; _is_shared = false; _data = 0; }
+    }
+
+    //! Construct image from an image file.
+    /**
+       Construct a new image instance with pixels of type \c T, and initialize pixel values with the data read from an image file.
+       \param filename : Input image filename.
+       \note
+       - Similar to CImg(unsigned int,unsigned int,unsigned int,unsigned int), but it reads the image
+         dimensions and pixel values from the specified image file.
+       - The recognition of the image file format by %CImg higly depends on the tools installed on your system
+         and on the external libraries you used to link your code against.
+       - Considered pixel type \c T should better fit the file format specification, or data loss may occur during file load
+         (e.g. constructing a \c CImg<unsigned char> from a float-valued image file).
+       - A \c CImgIOException is thrown when the specified \c filename cannot be read, or if the file format is not recognized.
+       \par Example
+       \code
+       const CImg<float> img("reference.jpg");
+       img.display();
+       \endcode
+       \image html ref_image.jpg
+       \see CImg(unsigned int,unsigned int,unsigned int,unsigned int),
+           assign(const char*).
+    **/
+    explicit CImg(const char *const filename):_width(0),_height(0),_depth(0),_spectrum(0),_is_shared(false),_data(0) {
+      assign(filename);
+    }
+
+    //! Copy constructor.
+    /**
+       Construct a new image instance with pixels of type \c T, as a copy of an existing \c CImg<t> instance.
+       \param img : Input image to copy.
+       \note
+       - Constructed copy has the same size width() x height() x depth() x spectrum() and pixel values as the input image \c img.
+       - If input image \c img is \e shared and if types \c T and \c t are the same, the constructed copy is also \e shared,
+         and shares its pixel buffer with \c img.
+         Modifying a pixel value in the constructed copy will thus also modifies it in the input image \c img.
+         This behavior is needful to allow functions to return shared images.
+       - Otherwise, the constructed copy allocates its own pixel buffer, and copies pixel values from the input image \c img
+         into its buffer. The copied pixel values may be eventually statically casted if types \c T and \c t are different.
+       - Constructing a copy from an image \c img when types \c t and \c T are the same is significantly faster than with different types.
+       - A \c CImgInstanceException is thrown when the pixel buffer cannot be allocated (e.g. not enough available memory).
+       \see CImg(const CImg<t>&,bool),
+           assign(const CImg<t>&),
+    **/
+    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; _spectrum = img._spectrum;
+        try { _data = new T[siz]; } catch (...) {
+          _width = _height = _depth = _spectrum = 0; _data = 0;
+          throw CImgInstanceException(_cimg_instance
+                                      "CImg() : Failed to allocate memory (%s) for image (%u,%u,%u,%u).",
+                                      cimg_instance,
+                                      cimg::strbuffersize(img._width*img._height*img._depth*img._spectrum*sizeof(T)),
+                                      img._width,img._height,img._depth,img._spectrum);
+        }
+        const t *ptrs = img._data + siz; cimg_for(*this,ptrd,T) *ptrd = (T)*(--ptrs);
+      } else { _width = _height = _depth = _spectrum = 0; _data = 0; }
+    }
+
+    //! Copy constructor \specialization.
+    CImg(const CImg<T>& img) {
+      const unsigned int siz = img.size();
+      if (img._data && siz) {
+        _width = img._width; _height = img._height; _depth = img._depth; _spectrum = img._spectrum; _is_shared = img._is_shared;
+        if (_is_shared) _data = const_cast<T*>(img._data);
+        else {
+          try { _data = new T[siz]; } catch (...) {
+            _width = _height = _depth = _spectrum = 0; _data = 0;
+            throw CImgInstanceException(_cimg_instance
+                                        "CImg() : Failed to allocate memory (%s) for image (%u,%u,%u,%u).",
+                                        cimg_instance,
+                                        cimg::strbuffersize(img._width*img._height*img._depth*img._spectrum*sizeof(T)),
+                                        img._width,img._height,img._depth,img._spectrum);
+
+          }
+          std::memcpy(_data,img._data,siz*sizeof(T));
+        }
+      } else { _width = _height = _depth = _spectrum = 0; _is_shared = false; _data = 0; }
+    }
+
+    //! Advanced copy constructor.
+    /**
+       Construct a new image instance with pixels of type \c T, as a copy of an existing \c CImg<t> instance,
+       while forcing the shared state of the constructed copy.
+       \param img : Input image to copy.
+       \param is_shared : Desired shared state of the constructed copy.
+       \note
+       - Similar to CImg(const CImg<t>&), except that it allows to decide the shared state of
+         the constructed image, which does not depend anymore on the shared state of the input image \c img :
+         - If \c is_shared is \c true, the constructed copy will share its pixel buffer with the input image \c img.
+           For that case, the pixel types \c T and \c t \e must be the same.
+         - If \c is_shared is \c false, the constructed copy will allocate its own pixel buffer, whether the input image \c img is
+           shared or not.
+       - A \c CImgArgumentException is thrown when a shared copy is requested with different pixel types \c T and \c t.
+       \see CImg(const CImg<t>&),
+           assign(const CImg<t>&,bool).
+    **/
+    template<typename t>
+    CImg(const CImg<t>& img, const bool is_shared):_is_shared(false) {
+      if (is_shared) {
+        _width = _height = _depth = _spectrum = 0; _data = 0;
+        throw CImgArgumentException(_cimg_instance
+                                    "CImg() : Invalid construction request of a shared instance from a "
+                                    "CImg<%s> image (%u,%u,%u,%u,%p) (pixel types are different).",
+                                    cimg_instance,
+                                    CImg<t>::pixel_type(),img._width,img._height,img._depth,img._spectrum,img._data);
+      }
+      const unsigned int siz = img.size();
+      if (img._data && siz) {
+        _width = img._width; _height = img._height; _depth = img._depth; _spectrum = img._spectrum;
+        try { _data = new T[siz]; } catch (...) {
+          _width = _height = _depth = _spectrum = 0; _data = 0;
+          throw CImgInstanceException(_cimg_instance
+                                      "CImg() : Failed to allocate memory (%s) for image (%u,%u,%u,%u).",
+                                      cimg_instance,
+                                      cimg::strbuffersize(img._width*img._height*img._depth*img._spectrum*sizeof(T)),
+                                      img._width,img._height,img._depth,img._spectrum);
+        }
+        const t *ptrs = img._data + siz; cimg_for(*this,ptrd,T) *ptrd = (T)*(--ptrs);
+      } else { _width = _height = _depth = _spectrum = 0; _data = 0; }
+    }
+
+    //! Advanced copy constructor \specialization.
+    CImg(const CImg<T>& img, const bool is_shared) {
+      const unsigned int siz = img.size();
+      if (img._data && siz) {
+        _width = img._width; _height = img._height; _depth = img._depth; _spectrum = img._spectrum; _is_shared = is_shared;
+        if (_is_shared) _data = const_cast<T*>(img._data);
+        else {
+          try { _data = new T[siz]; } catch (...) {
+            _width = _height = _depth = _spectrum = 0; _data = 0;
+            throw CImgInstanceException(_cimg_instance
+                                        "CImg() : Failed to allocate memory (%s) for image (%u,%u,%u,%u).",
+                                        cimg_instance,
+                                        cimg::strbuffersize(img._width*img._height*img._depth*img._spectrum*sizeof(T)),
+                                        img._width,img._height,img._depth,img._spectrum);
+          }
+          std::memcpy(_data,img._data,siz*sizeof(T));
+        }
+      } else { _width = _height = _depth = _spectrum = 0; _is_shared = false; _data = 0; }
+    }
+
+    //! Construct image with dimensions borrowed from another image.
+    /**
+       Construct a new image instance with pixels of type \c T, and size get from some dimensions of an existing \c CImg<t> instance.
+       \param img : Input image from which dimensions are borrowed.
+       \param dimensions : String describing the image size along the X,Y,Z and C-dimensions.
+       \note
+       - Similar to CImg(unsigned int,unsigned int,unsigned int,unsigned int), but it takes the image dimensions
+         (\e not its pixel values) from an existing \c CImg<t> instance.
+       - The allocated pixel buffer is \e not filled with a default value, and is likely to contain garbage values.
+         In order to initialize pixel values (e.g. with \c 0), use constructor CImg(const CImg<t>&,const char*,T) instead.
+       \par Example
+       \code
+       const CImg<float> img1(256,128,1,3),      // 'img1' is a 256x128x1x3 image.
+                         img2(img1,"xyzc"),      // 'img2' is a 256x128x1x3 image.
+                         img3(img1,"y,x,z,c"),   // 'img3' is a 128x256x1x3 image.
+                         img4(img1,"c,x,y,3",0), // 'img4' is a 3x128x256x3 image (with pixels initialized to '0').
+       \endcode
+       \see CImg(unsigned int,unsigned int,unsigned int,unsigned int),
+           CImg(const CImg<t>&,const char*,T),
+           assign(const CImg<t>&,const char*).
+     **/
+    template<typename t>
+    CImg(const CImg<t>& img, const char *const dimensions):_width(0),_height(0),_depth(0),_spectrum(0),_is_shared(false),_data(0) {
+      assign(img,dimensions);
+    }
+
+    //! Construct image with dimensions borrowed from another image and initialize pixel values.
+    /**
+       Construct a new image instance with pixels of type \c T, and size get from the dimensions of an existing \c CImg<t> instance,
+       and set all pixel values to specified \c value.
+       \param img : Input image from which dimensions are borrowed.
+       \param dimensions : String describing the image size along the X,Y,Z and V-dimensions.
+       \param value : Value used for initialization.
+       \note
+       - Similar to CImg(const CImg<t>&,const char*), but it also fills the pixel buffer with the specified \c value.
+       \see CImg(unsigned int,unsigned int,unsigned int,unsigned int,T),
+           CImg(const CImg<t>&,const char*),
+           assign(const CImg<t>&,const char*,T).
+     **/
+    template<typename t>
+    CImg(const CImg<t>& img, const char *const dimensions, const T value):
+      _width(0),_height(0),_depth(0),_spectrum(0),_is_shared(false),_data(0) {
+      assign(img,dimensions).fill(value);
+    }
+
+    //! Construct image from a display window.
+    /**
+       Construct a new image instance with pixels of type \c T, as a snapshot of an existing \c CImgDisplay instance.
+       \param disp : Input display window.
+       \note
+       - The width() and height() of the constructed image instance are the same as the specified \c CImgDisplay.
+       - The depth() and spectrum() of the constructed image instance are respectively set to \c 1 and \c 3 (i.e. a 2d color image).
+       - The image pixels are read as 8-bits RGB values.
+       \see CImgDisplay,
+           assign(const CImgDisplay&).
+     **/
+    explicit CImg(const CImgDisplay &disp):_width(0),_height(0),_depth(0),_spectrum(0),_is_shared(false),_data(0) {
+      disp.snapshot(*this);
+    }
+
+    //! In-place version of the default constructor/destructor.
+    /**
+       In-place version of the default constructor CImg(). It simply resets the instance to an empty image.
+       \note
+       - It reinitializes the current image instance to a new constructed image instance.
+       - Memory used by the previous pixel buffer of the image instance is deallocated if necessary (i.e. if instance was not empty nor shared).
+       - If the image instance was shared, it is replaced by a (non-shared) empty image without a deallocation process.
+       - It can be useful to force memory deallocation of a pixel buffer used by an image instance, before its formal destruction.
+       \see CImg(),
+           ~CImg().
+    **/
+    CImg<T>& assign() {
+      if (!_is_shared) delete[] _data;
+      _width = _height = _depth = _spectrum = 0; _is_shared = false; _data = 0;
+      return *this;
+    }
+
+    //! In-place version of a constructor.
+    /**
+       In-place version of the constructor CImg(unsigned int,unsigned int,unsigned int,unsigned int).
+       \param size_x : Desired image width().
+       \param size_y : Desired image height().
+       \param size_z : Desired image depth().
+       \param size_c : Desired image spectrum().
+       \note
+       - It reinitializes the current image instance to a new constructed image instance.
+       \see assign(unsigned int,unsigned int,unsigned int,unsigned int,T).
+           CImg(unsigned int,unsigned int,unsigned int,unsigned int).
+    **/
+    CImg<T>& assign(const unsigned int size_x, const unsigned int size_y=1, const unsigned int size_z=1, const unsigned int size_c=1) {
+      const unsigned int siz = size_x*size_y*size_z*size_c;
+      if (!siz) return assign();
+      const unsigned int curr_siz = size();
+      if (siz!=curr_siz) {
+        if (_is_shared)
+          throw CImgArgumentException(_cimg_instance
+                                      "assign() : Invalid assignement request of shared instance from specified image (%u,%u,%u,%u).",
+                                      cimg_instance,
+                                      size_x,size_y,size_z,size_c);
+        else {
+          delete[] _data;
+          try { _data = new T[siz]; } catch (...) {
+            _width = _height = _depth = _spectrum = 0; _data = 0;
+            throw CImgInstanceException(_cimg_instance
+                                        "assign() : Failed to allocate memory (%s) for image (%u,%u,%u,%u).",
+                                        cimg_instance,
+                                        cimg::strbuffersize(size_x*size_y*size_z*size_c*sizeof(T)),size_x,size_y,size_z,size_c);
+          }
+        }
+      }
+      _width = size_x; _height = size_y; _depth = size_z; _spectrum = size_c;
+      return *this;
+    }
+
+    //! In-place version of a constructor.
+    /**
+       In-place version of the constructor CImg(unsigned int,unsigned int,unsigned int,unsigned int,T).
+       \param size_x : Desired image width().
+       \param size_y : Desired image height().
+       \param size_z : Desired image depth().
+       \param size_c : Desired image spectrum().
+       \param value : Value for initialization.
+       \note
+       - It reinitializes the current image instance to a new constructed image instance.
+       \see assign(unsigned int,unsigned int,unsigned int,unsigned int),
+           CImg(unsigned int,unsigned int,unsigned int,unsigned int,T).
+    **/
+    CImg<T>& assign(const unsigned int size_x, const unsigned int size_y, const unsigned int size_z, const unsigned int size_c, const T value) {
+      return assign(size_x,size_y,size_z,size_c).fill(value);
+    }
+
+    //! In-place version of a constructor.
+    /**
+       In-place version of the constructor CImg(unsigned int,unsigned int,unsigned int,unsigned int,int,int,...).
+       \param size_x : Desired image width().
+       \param size_y : Desired image height().
+       \param size_z : Desired image depth().
+       \param size_c : Desired image spectrum().
+       \param value0 : First value of the initialization sequence (must be an integer).
+       \param value1 : Second value of the initialization sequence (must be an integer).
+       \param ...
+       \note
+       - It reinitializes the current image instance to a new constructed image instance.
+       \see assign(unsigned int,unsigned int,unsigned int,unsigned int,double,double,...),
+           CImg(unsigned int,unsigned int,unsigned int,unsigned int,int,int,...).
+    **/
+    CImg<T>& assign(const unsigned int size_x, const unsigned int size_y, const unsigned int size_z, const unsigned int size_c,
+                    const int value0, const int value1, ...) {
+      assign(size_x,size_y,size_z,size_c);
+      _CImg_stdarg(*this,value0,value1,size_x*size_y*size_z*size_c,int);
+      return *this;
+    }
+
+    //! In-place version of a constructor.
+    /**
+       In-place version of the constructor CImg(unsigned int,unsigned int,unsigned int,unsigned int,double,double,...).
+       \param size_x : Desired image width().
+       \param size_y : Desired image height().
+       \param size_z : Desired image depth().
+       \param size_c : Desired image spectrum().
+       \param value0 : First value of the initialization sequence (must be a double).
+       \param value1 : Second value of the initialization sequence (must be a double).
+       \param ...
+       \note
+       - It reinitializes the current image instance to a new constructed image instance.
+       \see assign(unsigned int,unsigned int,unsigned int,unsigned int,int,int,...),
+           CImg(unsigned int,unsigned int,unsigned int,unsigned int,double,double,...).
+    **/
+    CImg<T>& assign(const unsigned int size_x, const unsigned int size_y, const unsigned int size_z, const unsigned int size_c,
+                    const double value0, const double value1, ...) {
+      assign(size_x,size_y,size_z,size_c);
+      _CImg_stdarg(*this,value0,value1,size_x*size_y*size_z*size_c,double);
+      return *this;
+    }
+
+    //! In-place version of a constructor.
+    /**
+       In-place version of the constructor CImg(unsigned int,unsigned int,unsigned int,unsigned int,const char*,bool).
+       \param size_x : Desired image width().
+       \param size_y : Desired image height().
+       \param size_z : Desired image depth().
+       \param size_c : Desired image spectrum().
+       \param values : Value string describing the way pixel values are set.
+       \param repeat_values : Flag telling if filling process is periodic.
+       \note
+       - It reinitializes the current image instance to a new constructed image instance.
+       \see CImg(unsigned int,unsigned int,unsigned int,unsigned int,const char*,bool).
+    **/
+    CImg<T>& assign(const unsigned int size_x, const unsigned int size_y, const unsigned int size_z, const unsigned int size_c,
+                    const char *const values, const bool repeat_values) {
+      return assign(size_x,size_y,size_z,size_c).fill(values,repeat_values);
+    }
+
+    //! In-place version of a constructor.
+    /**
+       In-place version of the constructor CImg(const t*,unsigned int,unsigned int,unsigned int,unsigned int).
+       \param values : Pointer to the input memory buffer.
+       \param size_x : Desired image width().
+       \param size_y : Desired image height().
+       \param size_z : Desired image depth().
+       \param size_c : Desired image spectrum().
+       \note
+       - It reinitializes the current image instance to a new constructed image instance.
+       \see assign(const t*,unsigned int,unsigned int,unsigned int,unsigned int,bool).
+           CImg(const t*,unsigned int,unsigned int,unsigned int,unsigned int,bool).
+    **/
+    template<typename t>
+    CImg<T>& assign(const t *const values, const unsigned int size_x, const unsigned int size_y=1,
+                    const unsigned int size_z=1, const unsigned int size_c=1) {
+      const unsigned int siz = size_x*size_y*size_z*size_c;
+      if (!values || !siz) return assign();
+      assign(size_x,size_y,size_z,size_c);
+      const t *ptrs = values + siz; cimg_for(*this,ptrd,T) *ptrd = (T)*(--ptrs);
+      return *this;
+    }
+
+    //! In-place version of a constructor \specialization.
+    CImg<T>& assign(const T *const values, const unsigned int size_x, const unsigned int size_y=1,
+                    const unsigned int size_z=1, const unsigned int size_c=1) {
+      const unsigned int siz = size_x*size_y*size_z*size_c;
+      if (!values || !siz) return assign();
+      const unsigned int curr_siz = size();
+      if (values==_data && siz==curr_siz) return assign(size_x,size_y,size_z,size_c);
+      if (_is_shared || values+siz<_data || values>=_data+size()) {
+        assign(size_x,size_y,size_z,size_c);
+        if (_is_shared) std::memmove(_data,values,siz*sizeof(T));
+        else std::memcpy(_data,values,siz*sizeof(T));
+      } else {
+        T *new_data = 0;
+        try { new_data = new T[siz]; } catch (...) {
+          _width = _height = _depth = _spectrum = 0; _data = 0;
+          throw CImgInstanceException(_cimg_instance
+                                      "assign() : Failed to allocate memory (%s) for image (%u,%u,%u,%u).",
+                                      cimg_instance,
+                                      cimg::strbuffersize(size_x*size_y*size_z*size_c*sizeof(T)),size_x,size_y,size_z,size_c);
+        }
+        std::memcpy(new_data,values,siz*sizeof(T));
+        delete[] _data; _data = new_data; _width = size_x; _height = size_y; _depth = size_z; _spectrum = size_c;
+      }
+      return *this;
+    }
+
+    //! In-place version of a constructor.
+    /**
+       In-place version of the constructor CImg(const t*,unsigned int,unsigned int,unsigned int,unsigned int,bool).
+       \param values : Pointer to the input memory buffer.
+       \param size_x : Desired image width().
+       \param size_y : Desired image height().
+       \param size_z : Desired image depth().
+       \param size_c : Desired image spectrum().
+       \param is_shared : Flag telling if input memory buffer must be shared by the current instance.
+       \note
+       - It reinitializes the current image instance to a new constructed image instance.
+       \see assign(const t*,unsigned int,unsigned int,unsigned int,unsigned int).
+           CImg(const t*,unsigned int,unsigned int,unsigned int,unsigned int,bool).
+    **/
+    template<typename t>
+    CImg<T>& assign(const t *const values, const unsigned int size_x, const unsigned int size_y,
+                    const unsigned int size_z, const unsigned int size_c, const bool is_shared) {
+      if (is_shared)
+        throw CImgArgumentException(_cimg_instance
+                                    "assign() : Invalid assignment request of shared instance from (%s*) buffer"
+                                    "(pixel types are different).",
+                                    cimg_instance,
+                                    CImg<t>::pixel_type());
+      return assign(values,size_x,size_y,size_z,size_c);
+    }
+
+    //! In-place version of a constructor \specialization.
+    CImg<T>& assign(const T *const values, const unsigned int size_x, const unsigned int size_y,
+                    const unsigned int size_z, const unsigned int size_c, const bool is_shared) {
+      const unsigned int siz = size_x*size_y*size_z*size_c;
+      if (!values || !siz) {
+        if (is_shared)
+          throw CImgArgumentException(_cimg_instance
+                                      "assign() : Invalid assignment request of shared instance from (null) or empty buffer.",
+                                      cimg_instance);
+        else return assign();
+      }
+      if (!is_shared) { if (_is_shared) assign(); assign(values,size_x,size_y,size_z,size_c); }
+      else {
+        if (!_is_shared) {
+          if (values+siz<_data || values>=_data+size()) assign();
+          else cimg::warn(_cimg_instance
+                          "assign() : Shared image instance has overlapping memory.",
+                          cimg_instance);
+        }
+        _width = size_x; _height = size_y; _depth = size_z; _spectrum = size_c; _is_shared = true;
+        _data = const_cast<T*>(values);
+      }
+      return *this;
+    }
+
+    //! In-place version of a constructor.
+    /**
+       In-place version of the constructor CImg(const char*).
+       \param filename : Input image filename.
+       \note
+       - It reinitializes the current image instance to a new constructed image instance.
+       - Equivalent to load(const char*).
+       \see CImg(const char*),
+           load(const char*).
+    **/
+    CImg<T>& assign(const char *const filename) {
+      return load(filename);
+    }
+
+    //! In-place version of the default copy constructor.
+    /**
+       In-place version of the constructor CImg(const CImg<t>&).
+       \param img : Input image to copy.
+       \note
+       - It reinitializes the current image instance to a new constructed image instance.
+       \see assign(const CImg<t>&,bool),
+           CImg(const CImg<t>&).
+    **/
+    template<typename t>
+    CImg<T>& assign(const CImg<t>& img) {
+      return assign(img._data,img._width,img._height,img._depth,img._spectrum);
+    }
+
+    //! In-place version of the advanced copy constructor.
+    /**
+       In-place version of the constructor CImg(const CImg<t>&,bool).
+       \param img : Input image to copy.
+       \param is_shared : Desired shared state of the constructed copy.
+       \see assign(const CImg<t>&),
+           CImg(const CImg<t>&,bool).
+     **/
+    template<typename t>
+    CImg<T>& assign(const CImg<t>& img, const bool is_shared) {
+      return assign(img._data,img._width,img._height,img._depth,img._spectrum,is_shared);
+    }
+
+    //! In-place version of a constructor.
+    /**
+       In-place version of the constructor CImg(const CImg<t>&,const char*).
+       \param img : Input image from which dimensions are borrowed.
+       \param dimensions : String describing the image size along the X,Y,Z and V-dimensions.
+       \note
+       - It reinitializes the current image instance to a new constructed image instance.
+       \see assign(const CImg<t>&,const char*,T),
+           CImg(const CImg<t>&,const char*).
+     **/
+    template<typename t>
+    CImg<T>& assign(const CImg<t>& img, const char *const dimensions) {
+      if (!dimensions || !*dimensions) return assign(img._width,img._height,img._depth,img._spectrum);
+      unsigned int siz[4] = { 0,1,1,1 }, k = 0;
+      for (const char *s = dimensions; *s && k<4; ++k) {
+        char item[256] = { 0 };
+        if (std::sscanf(s,"%255[^0-9%xyzvwhdcXYZVWHDC]",item)>0) s+=std::strlen(item);
+        if (*s) {
+          unsigned int val = 0; char sep = 0;
+          if (std::sscanf(s,"%u%c",&val,&sep)>0) {
+            if (sep=='%') siz[k] = val*(k==0?_width:k==1?_height:k==2?_depth:_spectrum)/100;
+            else siz[k] = val;
+            while (*s>='0' && *s<='9') ++s; if (sep=='%') ++s;
+          } else switch (cimg::uncase(*s)) {
+          case 'x' : case 'w' : siz[k] = img._width; ++s; break;
+          case 'y' : case 'h' : siz[k] = img._height; ++s; break;
+          case 'z' : case 'd' : siz[k] = img._depth; ++s; break;
+          case 'c' : case 's' : siz[k] = img._spectrum; ++s; break;
+          default :
+            throw CImgArgumentException(_cimg_instance
+                                        "assign() : Invalid character '%c' detected in specified dimension string '%s'.",
+                                        cimg_instance,
+                                        *s,dimensions);
+          }
+        }
+      }
+      return assign(siz[0],siz[1],siz[2],siz[3]);
+    }
+
+    //! In-place version of a constructor.
+    /**
+       In-place version of the constructor CImg(const CImg<t>&,const char*,T).
+       \param img : Input image from which dimensions are borrowed.
+       \param dimensions : String describing the image size along the X,Y,Z and V-dimensions.
+       \param value : Value for initialization.
+       \note
+       - It reinitializes the current image instance to a new constructed image instance.
+       \see assign(const CImg<t>&,const char*),
+           CImg(const CImg<t>&,const char*,T).
+     **/
+    template<typename t>
+    CImg<T>& assign(const CImg<t>& img, const char *const dimensions, const T value) {
+      return assign(img,dimensions).fill(value);
+    }
+
+    //! In-place version of a constructor.
+    /**
+       In-place version of the constructor CImg(const CImgDisplay&).
+       \param disp : Input \c CImgDisplay.
+       \note
+       - It reinitializes the current image instance to a new constructed image instance.
+       \see CImg(const CImgDisplay&).
+    **/
+    CImg<T>& assign(const CImgDisplay &disp) {
+      disp.snapshot(*this);
+      return *this;
+    }
+
+    //! In-place version of the default constructor.
+    /**
+       Equivalent to assign().
+       \note
+       - It has been defined for compatibility with STL naming conventions.
+       \see assign().
+    **/
+    CImg<T>& clear() {
+      return assign();
+    }
+
+    //! Transfer content of an image instance into another one.
+    /**
+       Transfer the dimensions and the pixel buffer content of an image instance into another one,
+       and replace instance by an empty image. It avoids the copy of the pixel buffer
+       when possible.
+       \param img : Destination image.
+       \note
+       - Pixel types \c T and \c t of source and destination images can be different, though the process is designed to be
+         instantaneous when \c T and \c t are the same.
+       \par Example
+       \code
+       CImg<float> src(256,256,1,3,0), // Construct a 256x256x1x3 (color) image filled with value '0'.
+                   dest(16,16);        // Construct a 16x16x1x1 (scalar) image.
+       src.move_to(dest);              // Now, 'src' is empty and 'dest' is the 256x256x1x3 image.
+       \endcode
+       \see move_to(CImgList<t>&,unsigned int),
+           swap(CImg<T>&).
+    **/
+    template<typename t>
+    CImg<t>& move_to(CImg<t>& img) {
+      img.assign(*this);
+      assign();
+      return img;
+    }
+
+    //! Transfer content of an image instance into another one \specialization.
+    CImg<T>& move_to(CImg<T>& img) {
+      if (_is_shared || img._is_shared) img.assign(*this);
+      else swap(img);
+      assign();
+      return img;
+    }
+
+    //! Transfer content of an image instance into a new image in an image list.
+    /**
+       Transfer the dimensions and the pixel buffer content of an image instance
+       into a newly inserted image at position \c pos in specified \c CImgList<t> instance.
+       \param list : Destination list.
+       \param pos : Position of the newly inserted image in the list.
+       \note
+       - When optionnal parameter \c pos is ommited, the image instance is transfered as a new
+         image at the end of the specified \c list.
+       - It is convenient to sequentially insert new images into image lists, with no
+         additional copies of memory buffer.
+       \par Example
+       \code
+       CImgList<float> list;             // Construct an empty image list.
+       CImg<float> img("reference.jpg"); // Read image from filename.
+       img.move_to(list);                // Transfer image content as a new item in the list (no buffer copy).
+       \endcode
+       \see move_to(CImg<t>&),
+           swap(CImg<T>&).
+    **/
+    template<typename t>
+    CImgList<t>& move_to(CImgList<t>& list, const unsigned int pos=~0U) {
+      const unsigned int npos = pos>list._width?list._width:pos;
+      move_to(list.insert(1,npos)[npos]);
+      return list;
+    }
+
+    //! Swap fields of two image instances.
+    /**
+      \param img : Image to swap fields with.
+      \note
+      - It can be used to interchange the content of two images in a very fast way. Can be convenient when dealing
+        with algorithms requiring two swapping buffers.
+      \par Example
+      \code
+      CImg<float> img1("lena.jpg"),
+                  img2("milla.jpg");
+      img1.swap(img2);               // Now, 'img1' is 'milla' and 'img2' is 'lena'.
+      \endcode
+    **/
+    CImg<T>& swap(CImg<T>& img) {
+      cimg::swap(_width,img._width);
+      cimg::swap(_height,img._height);
+      cimg::swap(_depth,img._depth);
+      cimg::swap(_spectrum,img._spectrum);
+      cimg::swap(_data,img._data);
+      cimg::swap(_is_shared,img._is_shared);
+      return img;
+    }
+
+    //! Return a reference to an empty image.
+    /**
+       \note
+       This function is useful mainly to declare optional parameters having type \c CImg<T> in functions prototypes, e.g.
+       \code
+       void f(const int x=0, const int y=0, const CImg<float>& img=CImg<float>::empty());
+       \endcode
+     **/
+    static CImg<T>& empty() {
+      static CImg<T> _empty;
+      return _empty.assign();
+    }
+
+    //@}
+    //------------------------------------------
+    //
+    //! \name Overloaded Operators
+    //@{
+    //------------------------------------------
+
+    //! Access to a pixel value.
+    /**
+       Return a reference to a located pixel value of the image instance,
+       being possibly \e const, whether the image instance is \e const or not.
+       This is the standard method to get/set pixel values in \c CImg<T> images.
+       \param x X-coordinate of the pixel value.
+       \param y Y-coordinate of the pixel value.
+       \param z Z-coordinate of the pixel value.
+       \param c C-coordinate of the pixel value.
+       \note
+       - Range of pixel coordinates start from <tt>(0,0,0,0)</tt> to <tt>(width()-1,height()-1,depth()-1,spectrum()-1)</tt>.
+       - Due to the particular arrangement of the pixel buffers defined in %CImg, you can omit one coordinate if the corresponding dimension
+         is equal to \c 1.
+         For instance, pixels of a 2d image (depth() equal to \c 1) can be accessed by <tt>img(x,y,c)</tt> instead of <tt>img(x,y,0,c)</tt>.
+       \warning
+       - There is \e no boundary checking done in this operator, to make it as fast as possible.
+         You \e must take care of out-of-bounds access by yourself, if necessary.
+         For debuging purposes, you may want to define macro \c 'cimg_verbosity'>=3 to enable additional boundary checking operations
+         in this operator. In that case, warning messages will be printed on the error output when accessing out-of-bounds pixels.
+       \par Example
+       \code
+       CImg<float> img(100,100,1,3,0);                   // Construct a 100x100x1x3 (color) image with pixels set to '0'.
+       const float
+          valR = img(10,10,0,0),                         // Read red value at coordinates (10,10).
+          valG = img(10,10,0,1),                         // Read green value at coordinates (10,10)
+          valB = img(10,10,2),                           // Read blue value at coordinates (10,10) (Z-coordinate can be omitted).
+          avg = (valR + valG + valB)/3;                  // Compute average pixel value.
+       img(10,10,0) = img(10,10,1) = img(10,10,2) = avg; // Replace the color pixel (10,10) by the average grey value.
+       \endcode
+       \see at(),
+           atX(),
+           atXY(),
+           atXYZ(),
+           atXYZC().
+    **/
+#if cimg_verbosity>=3
+    T& operator()(const unsigned int x, const unsigned int y=0, const unsigned int z=0, const unsigned int c=0) {
+      const unsigned int off = (unsigned int)offset(x,y,z,c);
+      if (!_data || off>=size()) {
+        cimg::warn(_cimg_instance
+                   "operator() : Invalid pixel request, at coordinates (%u,%u,%u,%u) [offset=%u].",
+                   cimg_instance,
+                   x,y,z,c,off);
+        return *_data;
+      }
+      else return _data[off];
+    }
+
+    //! Access to a pixel value \const.
+    const T& operator()(const unsigned int x, const unsigned int y=0, const unsigned int z=0, const unsigned int c=0) const {
+      return const_cast<CImg<T>*>(this)->operator()(x,y,z,c);
+    }
+
+    //! Access to a pixel value.
+    /**
+       \param x : X-coordinate of the pixel value.
+       \param y : Y-coordinate of the pixel value.
+       \param z : Z-coordinate of the pixel value.
+       \param c : C-coordinate of the pixel value.
+       \param wh : Precomputed offset, must be equal to <tt>width()*\ref height()</tt>.
+       \param whd : Precomputed offset, must be equal to <tt>width()*\ref height()*\ref depth()</tt>.
+       \note
+       - Similar to (but faster than) operator()().
+         It uses precomputed offsets to optimize memory access. You may use it to optimize
+         the reading/writing of several pixel values in the same image (e.g. in a loop).
+       \see operator()().
+     **/
+    T& operator()(const unsigned int x, const unsigned int y, const unsigned int z, const unsigned int c,
+                  const unsigned long wh, const unsigned long whd=0) {
+      cimg::unused(wh,whd);
+      return (*this)(x,y,z,c);
+    }
+
+    //! Access to a pixel value \const.
+    const T& operator()(const unsigned int x, const unsigned int y, const unsigned int z, const unsigned int c,
+                        const unsigned long wh, const unsigned long whd=0) const {
+      cimg::unused(wh,whd);
+      return (*this)(x,y,z,c);
+    }
+#else
+    T& operator()(const unsigned int x) {
+      return _data[x];
+    }
+
+    const T& operator()(const unsigned int x) const {
+      return _data[x];
+    }
+
+    T& operator()(const unsigned int x, const unsigned int y) {
+      return _data[x + y*_width];
+    }
+
+    const T& operator()(const unsigned int x, const unsigned int y) const {
+      return _data[x + y*_width];
+    }
+
+    T& operator()(const unsigned int x, const unsigned int y, const unsigned int z) {
+      return _data[x + y*_width + z*_width*_height];
+   }
+
+    const T& operator()(const unsigned int x, const unsigned int y, const unsigned int z) const {
+      return _data[x + y*_width + z*_width*_height];
+    }
+
+    T& operator()(const unsigned int x, const unsigned int y, const unsigned int z, const unsigned int c) {
+      return _data[x + y*_width + z*_width*_height + c*_width*_height*_depth];
+    }
+
+    const T& operator()(const unsigned int x, const unsigned int y, const unsigned int z, const unsigned int c) const {
+      return _data[x + y*_width + z*_width*_height + c*_width*_height*_depth];
+    }
+
+    T& operator()(const unsigned int x, const unsigned int y, const unsigned int z, const unsigned int,
+                  const unsigned long wh) {
+      return _data[x + y*_width + z*wh];
+    }
+
+    const T& operator()(const unsigned int x, const unsigned int y, const unsigned int z, const unsigned int,
+                        const unsigned long wh) const {
+      return _data[x + y*_width + z*wh];
+    }
+
+    T& operator()(const unsigned int x, const unsigned int y, const unsigned int z, const unsigned int c,
+                  const unsigned long wh, const unsigned long whd) {
+      return _data[x + y*_width + z*wh + c*whd];
+    }
+
+    const T& operator()(const unsigned int x, const unsigned int y, const unsigned int z, const unsigned int c,
+                        const unsigned long wh, const unsigned long whd) const {
+      return _data[x + y*_width + z*wh + c*whd];
+    }
+#endif
+
+    //! Implicitely cast an image into a \c T*.
+    /**
+       Implicitely cast a \c CImg<T> instance into a \c T* or \c const \c T* pointer, whether the image instance
+       is \e const or not. The returned pointer points on the first value of the image pixel buffer.
+       \note
+       - It simply returns the pointer data() to the pixel buffer.
+       - This implicit conversion is convenient to test the empty state of images (data() being \c 0 in this case), e.g.
+       \code
+       CImg<float> img1(100,100), img2; // 'img1' is a 100x100 image, 'img2' is an empty image.
+       if (img1) {                      // Test succeeds, 'img1' is not an empty image.
+         if (!img2) {                   // Test succeeds, 'img2' is an empty image.
+           std::printf("'img1' is not empty, 'img2' is empty.");
+         }
+       }
+       \endcode
+       - It also allows to use brackets to access pixel values, without need for a \c CImg<T>::operator[](), e.g.
+       \code
+       CImg<float> img(100,100);
+       const float value = img[99]; // Access to value of the last pixel on the first line.
+       img[510] = 255;              // Set pixel value at (10,5).
+       \endcode
+       \see operator()().
+    **/
+    operator T*() {
+      return _data;
+    }
+
+    //! Implicitely cast an image into a \c T* \const.
+    operator const T*() const {
+      return _data;
+    }
+
+    //! Assign a value to all image pixels.
+    /**
+       Assign specified \c value to each pixel value of the image instance.
+       \param value : Value that will be assigned to image pixels.
+       \note
+       - The image size is never modified.
+       - The \c value may be casted to pixel type \c T if necessary.
+       \par Example
+       \code
+       CImg<char> img(100,100); // Declare image (with garbage values).
+       img = 0;                 // Set all pixel values to '0'.
+       img = 1.2;               // Set all pixel values to '1' (cast of '1.2' as a 'char').
+       \endcode
+       \see fill(const T).
+    **/
+    CImg<T>& operator=(const T value) {
+      return fill(value);
+    }
+
+    //! Assign pixels values from a specified expression.
+    /**
+       Initialize all pixel values from the specified string \c expression.
+       \param expression : Value string describing the way pixel values are set.
+       \note
+       - String parameter \c expression may describe different things :
+         - If \c expression is a list of values (as in \c "1,2,3,8,3,2"), or a formula (as in \c "(x*y)%255"),
+           the pixel values are set from specified \c expression and the image size is not modified.
+         - If \c expression is a filename (as in \c "reference.jpg"), the corresponding image file is loaded and replace the image instance.
+           The image size is modified if necessary.
+       \par Example
+       \code
+       CImg<float> img1(100,100), img2(img1), img3(img1); // Declare three 100x100 scalar images with unitialized pixel values.
+       img1 = "0,50,100,150,200,250,200,150,100,50";      // Set pixel values of 'img1' from a value sequence.
+       img2 = "10*((x*y)%25)";                            // Set pixel values of 'img2' from a formula.
+       img3 = "reference.jpg";                            // Set pixel values of 'img3' from a file (image size is modified).
+       (img1,img2,img3).display();
+       \endcode
+       \image html ref_operator_eq.jpg
+       \see fill(const char*, bool),
+           load(const char*).
+    **/
+    CImg<T>& operator=(const char *const expression) {
+      const unsigned int omode = cimg::exception_mode();
+      cimg::exception_mode() = 0;
+      try {
+        fill(expression,true);
+      } catch (CImgException&) {
+        cimg::exception_mode() = omode;
+        load(expression);
+      }
+      cimg::exception_mode() = omode;
+      return *this;
+    }
+
+    //! Copy an image into the current image instance.
+    /**
+       Similar to the in-place copy constructor assign(const CImg<t>&).
+    **/
+    template<typename t>
+    CImg<T>& operator=(const CImg<t>& img) {
+      return assign(img);
+    }
+
+    //! Copy an image into the current image instance \specialization.
+    CImg<T>& operator=(const CImg<T>& img) {
+      return assign(img);
+    }
+
+    //! Copy the content of a display window to the current image instance.
+    /**
+       Similar to assign(const CImgDisplay&).
+    **/
+    CImg<T>& operator=(const CImgDisplay& disp) {
+      disp.snapshot(*this);
+      return *this;
+    }
+
+    //! In-place addition operator.
+    /**
+       Add specified \c value to all pixels of an image instance.
+       \param value : Value to add.
+       \note
+       - Resulting pixel values are casted to fit the pixel type \c T. For instance, adding \c 0.2 to a \c CImg<char> is possible but does nothing indeed.
+       - Overflow values are treated as with standard C++ numeric types. For instance,
+       \code
+       CImg<unsigned char> img(100,100,1,1,255); // Construct a 100x100 image with pixel values '255'.
+       img+=1;                                   // Add '1' to each pixels -> Overflow.
+       // here all pixels of image 'img' are equal to '0'.
+       \endcode
+       - To prevent value overflow, you may want to consider pixel type \c T as \c float or \c double, and use cut() after addition.
+       \par Example
+       \code
+       CImg<unsigned char> img1("reference.jpg");          // Load a 8-bits RGB image (values in [0,255]).
+       CImg<float> img2(img1);                             // Construct a float-valued copy of 'img1'.
+       img2+=100;                                          // Add '100' to pixel values -> goes out of [0,255] but no problems with floats.
+       img2.cut(0,255);                                    // Cut values in [0,255] to fit the 'unsigned char' constraint.
+       img1 = img2;                                        // Rewrite safe result in 'unsigned char' version 'img1'.
+       const CImg<unsigned char> img3 = (img1 + 100).cut(0,255); // Do the same in a more simple and elegant way.
+       (img1,img2,img3).display();
+       \endcode
+       \image html ref_operator_plus.jpg
+       \see operator+(const t) const,
+           operator-=(const t),
+           operator*=(const t),
+           operator/=(const t),
+           operator%=(const t),
+           operator&=(const t),
+           operator|=(const t),
+           operator^=(const t),
+           operator<<=(const t),
+           operator>>=(const t).
+     **/
+    template<typename t>
+    CImg<T>& operator+=(const t value) {
+      cimg_for(*this,ptrd,T) *ptrd = (T)(*ptrd + value);
+      return *this;
+    }
+
+    //! In-place addition operator.
+    /**
+       Add values to image pixels, according to the specified string \c expression.
+       \param expression : Value string describing the way pixel values are added.
+       \note
+       - Similar to operator=(const char*), except that it adds values to the pixels of the current image instance,
+         instead of assigning them.
+       \see operator+=(const t),
+           operator=(const char*),
+           operator+(const char*) const,
+           operator-=(const char*),
+           operator*=(const char*),
+           operator/=(const char*),
+           operator%=(const char*),
+           operator&=(const char*),
+           operator|=(const char*),
+           operator^=(const char*),
+           operator<<=(const char*),
+           operator>>=(const char*).
+    **/
+    CImg<T>& operator+=(const char *const expression) {
+      const unsigned int omode = cimg::exception_mode();
+      cimg::exception_mode() = 0;
+      try {
+        const CImg<T> _base = std::strstr(expression,"i(")?+*this:CImg<T>(), &base = _base?_base:*this;
+        _cimg_math_parser mp(base,expression,"operator+=");
+        T *ptrd = _data;
+        cimg_forXYZC(*this,x,y,z,c) { *ptrd = (T)(*ptrd + mp.eval(x,y,z,c)); ++ptrd; }
+      } catch (CImgException&) {
+        cimg::exception_mode() = omode;
+        *this+=CImg<T>(_width,_height,_depth,_spectrum,expression,true);
+      }
+      cimg::exception_mode() = omode;
+      return *this;
+    }
+
+    //! In-place addition operator.
+    /**
+       Add values to image pixels, according to the values of the input image \c img.
+       \param img : Input image to add.
+       \note
+       - The size of the image instance is never modified.
+       - It is not mandatory that input image \c img has the same size as the image instance. If less values are available
+         in \c img, then the values are added cyclically. For instance, adding one WxH scalar image (spectrum() equal to \c 1) to
+         one WxH color image (spectrum() equal to \c 3) means each color channel will be incremented with the same values at the same
+         locations.
+       \par Example
+       \code
+       CImg<float> img1("reference.jpg");                                   // Load a RGB color image (img1.spectrum()==3)
+       const CImg<float> img2(img1.width(),img.height(),1,1,"255*(x/w)^2"); // Construct a scalar shading (img2.spectrum()==1).
+       img1+=img2;                                                          // Add shading to each channel of 'img1'.
+       img1.cut(0,255);                                                     // Prevent [0,255] overflow.
+       (img2,img1).display();
+       \endcode
+       \image html ref_operator_plus1.jpg
+       \see operator+(const CImg<t>&) const,
+           operator=(const CImg<t>&),
+           operator-=(const CImg<t>&),
+           operator*=(const CImg<t>&),
+           operator/=(const CImg<t>&),
+           operator%=(const CImg<t>&),
+           operator&=(const CImg<t>&),
+           operator|=(const CImg<t>&),
+           operator^=(const CImg<t>&),
+           operator<<=(const CImg<t>&),
+           operator>>=(const CImg<t>&).
+    **/
+    template<typename t>
+    CImg<T>& operator+=(const CImg<t>& img) {
+      const unsigned int siz = size(), isiz = img.size();
+      if (siz && isiz) {
+        if (is_overlapped(img)) return *this+=+img;
+        T *ptrd = _data, *const ptre = _data + siz;
+        if (siz>isiz) for (unsigned int n = siz/isiz; n; --n)
+          for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs<ptrs_end; ++ptrd) *ptrd = (T)(*ptrd + *(ptrs++));
+        for (const t *ptrs = img._data; ptrd<ptre; ++ptrd) *ptrd = (T)(*ptrd + *(ptrs++));
+      }
+      return *this;
+    }
+
+    //! In-place increment operator (prefix).
+    /**
+       Add \c 1 to all image pixels, and return a reference to the current incremented image instance.
+       \note
+       - Writing \c ++img is equivalent to \c img+=1.
+       \see operator++(int),
+           operator--().
+     **/
+    CImg<T>& operator++() {
+      cimg_for(*this,ptrd,T) ++*ptrd;
+      return *this;
+    }
+
+    //! In-place increment operator (postfix).
+    /**
+       Add \c 1 to all image pixels, and return a new copy of the initial (pre-incremented) image instance.
+       \note
+       - Use the prefixed version operator++() if you don't need a copy of the initial (pre-incremented) image instance, since
+         a useless image copy may be expensive in terms of memory usage.
+       \see operator++(),
+           operator--(int).
+     **/
+    CImg<T> operator++(int) {
+      const CImg<T> copy(*this,false);
+      ++*this;
+      return copy;
+    }
+
+    //! Return a non-shared copy of the image instance.
+    /**
+       \note
+       - Use this operator to ensure you get a non-shared copy of an image instance with same pixel type \c T.
+         Indeed, the usual copy constructor CImg<T>(const CImg<T>&) returns a shared copy of a shared input image, and it may be
+         not desirable to work on a regular copy (e.g. for a resize operation) if you have no informations about the shared state
+         of the input image.
+       - Writing \c (+img) is equivalent to \c CImg<T>(img,false).
+       \see CImg(const CImg<T>&),
+           CImg(const CImg<T>&,bool),
+           operator-() const,
+           operator~() const.
+    **/
+    CImg<T> operator+() const {
+      return CImg<T>(*this,false);
+    }
+
+    //! Addition operator.
+    /**
+       Similar to operator+=(const t), except that it returns a new image instance instead of operating in-place.
+       The pixel type of the returned image may be a superset of the initial pixel type \c T, if necessary.
+     **/
+    template<typename t>
+    CImg<_cimg_Tt> operator+(const t value) const {
+      return CImg<_cimg_Tt>(*this,false)+=value;
+    }
+
+    //! Addition operator.
+    /**
+       Similar to operator+=(const char*), except that it returns a new image instance instead of operating in-place.
+       The pixel type of the returned image may be a superset of the initial pixel type \c T, if necessary.
+     **/
+    CImg<Tfloat> operator+(const char *const expression) const {
+      return CImg<Tfloat>(*this,false)+=expression;
+    }
+
+    //! Addition operator.
+    /**
+       Similar to operator+=(const CImg<t>&), except that it returns a new image instance instead of operating in-place.
+       The pixel type of the returned image may be a superset of the initial pixel type \c T, if necessary.
+     **/
+    template<typename t>
+    CImg<_cimg_Tt> operator+(const CImg<t>& img) const {
+      return CImg<_cimg_Tt>(*this,false)+=img;
+    }
+
+    //! In-place substraction operator.
+    /**
+       Similar to operator+=(const t), except that it performs a substraction instead of an addition.
+     **/
+    template<typename t>
+    CImg<T>& operator-=(const t value) {
+      cimg_for(*this,ptrd,T) *ptrd = (T)(*ptrd - value);
+      return *this;
+    }
+
+    //! In-place substraction operator.
+    /**
+       Similar to operator+=(const char*), except that it performs a substraction instead of an addition.
+     **/
+    CImg<T>& operator-=(const char *const expression) {
+      const unsigned int omode = cimg::exception_mode();
+      cimg::exception_mode() = 0;
+      try {
+        const CImg<T> _base = std::strstr(expression,"i(")?+*this:CImg<T>(), &base = _base?_base:*this;
+        _cimg_math_parser mp(base,expression,"operator-=");
+        T *ptrd = _data;
+        cimg_forXYZC(*this,x,y,z,c) { *ptrd = (T)(*ptrd - mp.eval(x,y,z,c)); ++ptrd; }
+      } catch (CImgException&) {
+        cimg::exception_mode() = omode;
+        *this-=CImg<T>(_width,_height,_depth,_spectrum,expression,true);
+      }
+      cimg::exception_mode() = omode;
+      return *this;
+    }
+
+    //! In-place substraction operator.
+    /**
+       Similar to operator+=(const CImg<t>&), except that it performs a substraction instead of an addition.
+     **/
+    template<typename t>
+    CImg<T>& operator-=(const CImg<t>& img) {
+      const unsigned int siz = size(), isiz = img.size();
+      if (siz && isiz) {
+        if (is_overlapped(img)) return *this-=+img;
+        T *ptrd = _data, *const ptre = _data + siz;
+        if (siz>isiz) for (unsigned int n = siz/isiz; n; --n)
+          for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs<ptrs_end; ++ptrd) *ptrd = (T)(*ptrd - *(ptrs++));
+        for (const t *ptrs = img._data; ptrd<ptre; ++ptrd) *ptrd = (T)(*ptrd - *(ptrs++));
+      }
+      return *this;
+    }
+
+    //! In-place decrement operator (prefix).
+    /**
+       Similar to operator++(), except that it performs a decrement instead of an increment.
+    **/
+    CImg<T>& operator--() {
+      cimg_for(*this,ptrd,T) *ptrd = *ptrd-(T)1;
+      return *this;
+    }
+
+    //! In-place decrement operator (postfix).
+    /**
+       Similar to operator++(int), except that it performs a decrement instead of an increment.
+    **/
+    CImg<T> operator--(int) {
+      const CImg<T> copy(*this,false);
+      --*this;
+      return copy;
+    }
+
+    //! Replace each pixel by its opposite value.
+    /**
+       \note
+       - If the computed opposite values are out-of-range, they are treated as with standard C++ numeric types. For instance,
+         the \c unsigned \c char opposite of \c 1 is \c 255.
+       \par Example
+       \code
+       const CImg<unsigned char>
+         img1("reference.jpg"),   // Load a RGB color image.
+         img2 = -img1;            // Compute its opposite (in 'unsigned char').
+       (img1,img2).display();
+       \endcode
+       \image html ref_operator_minus.jpg
+       \see operator+(),
+           operator~().
+     **/
+    CImg<T> operator-() const {
+      return CImg<T>(_width,_height,_depth,_spectrum,(T)0)-=*this;
+    }
+
+    //! Substraction operator.
+    /**
+       Similar to operator-=(const t), except that it returns a new image instance instead of operating in-place.
+       The pixel type of the returned image may be a superset of the initial pixel type \c T, if necessary.
+    **/
+    template<typename t>
+    CImg<_cimg_Tt> operator-(const t value) const {
+      return CImg<_cimg_Tt>(*this,false)-=value;
+    }
+
+    //! Substraction operator.
+    /**
+       Similar to operator-=(const char*), except that it returns a new image instance instead of operating in-place.
+       The pixel type of the returned image may be a superset of the initial pixel type \c T, if necessary.
+    **/
+    CImg<Tfloat> operator-(const char *const expression) const {
+      return CImg<Tfloat>(*this,false)-=expression;
+    }
+
+    //! Substraction operator.
+    /**
+       Similar to operator-=(const CImg<t>&), except that it returns a new image instance instead of operating in-place.
+       The pixel type of the returned image may be a superset of the initial pixel type \c T, if necessary.
+    **/
+    template<typename t>
+    CImg<_cimg_Tt> operator-(const CImg<t>& img) const {
+      return CImg<_cimg_Tt>(*this,false)-=img;
+    }
+
+    //! In-place multiplication operator.
+    /**
+       Similar to operator+=(const t), except that it performs a multiplication instead of an addition.
+     **/
+    template<typename t>
+    CImg<T>& operator*=(const t value) {
+      cimg_for(*this,ptrd,T) *ptrd = (T)(*ptrd * value);
+      return *this;
+    }
+
+    //! In-place multiplication operator.
+    /**
+       Similar to operator+=(const char*), except that it performs a multiplication instead of an addition.
+     **/
+    CImg<T>& operator*=(const char *const expression) {
+      const unsigned int omode = cimg::exception_mode();
+      cimg::exception_mode() = 0;
+      try {
+        const CImg<T> _base = std::strstr(expression,"i(")?+*this:CImg<T>(), &base = _base?_base:*this;
+        _cimg_math_parser mp(base,expression,"operator*=");
+        T *ptrd = _data;
+        cimg_forXYZC(*this,x,y,z,c) { *ptrd = (T)(*ptrd * mp.eval(x,y,z,c)); ++ptrd; }
+      } catch (CImgException&) {
+        cimg::exception_mode() = omode;
+        mul(CImg<T>(_width,_height,_depth,_spectrum,expression,true));
+      }
+      cimg::exception_mode() = omode;
+      return *this;
+    }
+
+    //! In-place multiplication operator.
+    /**
+       Replace the image instance by the matrix multiplication between the image instance and the specified matrix \c img.
+       \param img : Second operand of the matrix multiplication.
+       \note
+       - It does \e not compute a pointwise multiplication between two images. For this purpose, use mul(const CImg<t>&) instead.
+       - The size of the image instance can be modified by this operator.
+       \par Example
+       \code
+       CImg<float> A(2,2,1,1, 1,2,3,4);   // Construct 2x2 matrix A = [1,2;3,4].
+       const CImg<float> X(1,2,1,1, 1,2); // Construct 1x2 vector X = [1;2].
+       A*=X;                              // Assign matrix multiplication A*X to 'A'.
+       // 'A' is now a 1x2 vector whose values are [5;11].
+       \endcode
+       \see operator*(const CImg<t>&) const,
+           mul().
+    **/
+    template<typename t>
+    CImg<T>& operator*=(const CImg<t>& img) {
+      return ((*this)*img).move_to(*this);
+    }
+
+    //! Multiplication operator.
+    /**
+       Similar to operator*=(const t), except that it returns a new image instance instead of operating in-place.
+       The pixel type of the returned image may be a superset of the initial pixel type \c T, if necessary.
+    **/
+    template<typename t>
+    CImg<_cimg_Tt> operator*(const t value) const {
+      return CImg<_cimg_Tt>(*this,false)*=value;
+    }
+
+    //! Multiplication operator.
+    /**
+       Similar to operator*=(const char*), except that it returns a new image instance instead of operating in-place.
+       The pixel type of the returned image may be a superset of the initial pixel type \c T, if necessary.
+    **/
+    CImg<Tfloat> operator*(const char *const expression) const {
+      return CImg<Tfloat>(*this,false)*=expression;
+    }
+
+    //! Multiplication operator.
+    /**
+       Similar to operator*=(const CImg<t>&), except that it returns a new image instance instead of operating in-place.
+       The pixel type of the returned image may be a superset of the initial pixel type \c T, if necessary.
+    **/
+    template<typename t>
+    CImg<_cimg_Tt> operator*(const CImg<t>& img) const {
+      if (_width!=img._height || _depth!=1 || _spectrum!=1)
+        throw CImgArgumentException(_cimg_instance
+                                    "operator*() : Invalid multiplication of instance by specified matrix (%u,%u,%u,%u,%p)",
+                                    cimg_instance,
+                                    img._width,img._height,img._depth,img._spectrum,img._data);
+
+      CImg<_cimg_Tt> res(img._width,_height);
+      _cimg_Tt value, *ptrd = res._data;
+#ifdef cimg_use_openmp
+#pragma omp parallel for if (size()>=1000 && img.size()>=1000) private(value)
+#endif
+      cimg_forXY(res,i,j) { value = 0; cimg_forX(*this,k) value+=(*this)(k,j)*img(i,k); *(ptrd++) = value; }
+      return res;
+    }
+
+    //! In-place division operator.
+    /**
+       Similar to operator+=(const t), except that it performs a division instead of an addition.
+     **/
+    template<typename t>
+    CImg<T>& operator/=(const t value) {
+      cimg_for(*this,ptrd,T) *ptrd = (T)(*ptrd / value);
+      return *this;
+    }
+
+    //! In-place division operator.
+    /**
+       Similar to operator+=(const char*), except that it performs a division instead of an addition.
+     **/
+    CImg<T>& operator/=(const char *const expression) {
+      const unsigned int omode = cimg::exception_mode();
+      cimg::exception_mode() = 0;
+      try {
+        const CImg<T> _base = std::strstr(expression,"i(")?+*this:CImg<T>(), &base = _base?_base:*this;
+        _cimg_math_parser mp(base,expression,"operator/=");
+        T *ptrd = _data;
+        cimg_forXYZC(*this,x,y,z,c) { *ptrd = (T)(*ptrd / mp.eval(x,y,z,c)); ++ptrd; }
+      } catch (CImgException&) {
+        cimg::exception_mode() = omode;
+        div(CImg<T>(_width,_height,_depth,_spectrum,expression,true));
+      }
+      cimg::exception_mode() = omode;
+      return *this;
+    }
+
+    //! In-place division operator.
+    /**
+       Replace the image instance by the (right) matrix division between the image instance and the specified matrix \c img.
+       \param img : Second operand of the matrix division.
+       \note
+       - It does \e not compute a pointwise division between two images. For this purpose, use div(const CImg<t>&) instead.
+       - It returns the matrix operation \c A*inverse(img).
+       - The size of the image instance can be modified by this operator.
+       \see operator/(const CImg<t>&) const,
+           operator*(const CImg<t>&) const,
+           div().
+     **/
+    template<typename t>
+    CImg<T>& operator/=(const CImg<t>& img) {
+      return (*this*img.get_invert()).move_to(*this);
+    }
+
+    //! Division operator.
+    /**
+       Similar to operator/=(const t), except that it returns a new image instance instead of operating in-place.
+       The pixel type of the returned image may be a superset of the initial pixel type \c T, if necessary.
+    **/
+    template<typename t>
+    CImg<_cimg_Tt> operator/(const t value) const {
+      return CImg<_cimg_Tt>(*this,false)/=value;
+    }
+
+    //! Division operator.
+    /**
+       Similar to operator/=(const char*), except that it returns a new image instance instead of operating in-place.
+       The pixel type of the returned image may be a superset of the initial pixel type \c T, if necessary.
+    **/
+    CImg<Tfloat> operator/(const char *const expression) const {
+      return CImg<Tfloat>(*this,false)/=expression;
+    }
+
+    //! Division operator.
+    /**
+       Similar to operator/=(const CImg<t>&), except that it returns a new image instance instead of operating in-place.
+       The pixel type of the returned image may be a superset of the initial pixel type \c T, if necessary.
+    **/
+    template<typename t>
+    CImg<_cimg_Tt> operator/(const CImg<t>& img) const {
+      return (*this)*img.get_invert();
+    }
+
+    //! In-place modulo operator.
+    /**
+       Similar to operator+=(const t), except that it performs a modulo operation instead of an addition.
+    **/
+    template<typename t>
+    CImg<T>& operator%=(const t value) {
+      cimg_for(*this,ptrd,T) *ptrd = (T)cimg::mod(*ptrd,(T)value);
+      return *this;
+    }
+
+    //! In-place modulo operator.
+    /**
+       Similar to operator+=(const char*), except that it performs a modulo operation instead of an addition.
+    **/
+    CImg<T>& operator%=(const char *const expression) {
+      const unsigned int omode = cimg::exception_mode();
+      cimg::exception_mode() = 0;
+      try {
+        const CImg<T> _base = std::strstr(expression,"i(")?+*this:CImg<T>(), &base = _base?_base:*this;
+        _cimg_math_parser mp(base,expression,"operator%=");
+        T *ptrd = _data;
+        cimg_forXYZC(*this,x,y,z,c) { *ptrd = (T)cimg::mod(*ptrd,(T)mp.eval(x,y,z,c)); ++ptrd; }
+      } catch (CImgException&) {
+        cimg::exception_mode() = omode;
+        *this%=CImg<T>(_width,_height,_depth,_spectrum,expression,true);
+      }
+      cimg::exception_mode() = omode;
+      return *this;
+    }
+
+    //! In-place modulo operator.
+    /**
+       Similar to operator+=(const CImg<t>&), except that it performs a modulo operation instead of an addition.
+    **/
+    template<typename t>
+    CImg<T>& operator%=(const CImg<t>& img) {
+      const unsigned int siz = size(), isiz = img.size();
+      if (siz && isiz) {
+        if (is_overlapped(img)) return *this%=+img;
+        T *ptrd = _data, *const ptre = _data + siz;
+        if (siz>isiz) for (unsigned int n = siz/isiz; n; --n)
+          for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs<ptrs_end; ++ptrd) *ptrd = cimg::mod(*ptrd,(T)*(ptrs++));
+        for (const t *ptrs = img._data; ptrd<ptre; ++ptrd) *ptrd = cimg::mod(*ptrd,(T)*(ptrs++));
+      }
+      return *this;
+    }
+
+    //! Modulo operator.
+    /**
+       Similar to operator%=(const t), except that it returns a new image instance instead of operating in-place.
+       The pixel type of the returned image may be a superset of the initial pixel type \c T, if necessary.
+    **/
+    template<typename t>
+    CImg<_cimg_Tt> operator%(const t value) const {
+      return CImg<_cimg_Tt>(*this,false)%=value;
+    }
+
+    //! Modulo operator.
+    /**
+       Similar to operator%=(const char*), except that it returns a new image instance instead of operating in-place.
+       The pixel type of the returned image may be a superset of the initial pixel type \c T, if necessary.
+    **/
+    CImg<Tfloat> operator%(const char *const expression) const {
+      return CImg<Tfloat>(*this,false)%=expression;
+    }
+
+    //! Modulo operator.
+    /**
+       Similar to operator%=(const CImg<t>&), except that it returns a new image instance instead of operating in-place.
+       The pixel type of the returned image may be a superset of the initial pixel type \c T, if necessary.
+    **/
+    template<typename t>
+    CImg<_cimg_Tt> operator%(const CImg<t>& img) const {
+      return CImg<_cimg_Tt>(*this,false)%=img;
+    }
+
+    //! In-place bitwise AND operator.
+    /**
+       Similar to operator+=(const t), except that it performs a bitwise AND operation instead of an addition.
+    **/
+    template<typename t>
+    CImg<T>& operator&=(const t value) {
+      cimg_for(*this,ptrd,T) *ptrd = (T)((unsigned long)*ptrd & (unsigned long)value);
+      return *this;
+    }
+
+    //! In-place bitwise AND operator.
+    /**
+       Similar to operator+=(const char*), except that it performs a bitwise AND operation instead of an addition.
+    **/
+    CImg<T>& operator&=(const char *const expression) {
+      const unsigned int omode = cimg::exception_mode();
+      cimg::exception_mode() = 0;
+      try {
+        const CImg<T> _base = std::strstr(expression,"i(")?+*this:CImg<T>(), &base = _base?_base:*this;
+        _cimg_math_parser mp(base,expression,"operator&=");
+        T *ptrd = _data;
+        cimg_forXYZC(*this,x,y,z,c) { *ptrd = (T)((unsigned long)*ptrd & (unsigned long)mp.eval(x,y,z,c)); ++ptrd; }
+      } catch (CImgException&) {
+        cimg::exception_mode() = omode;
+        *this&=CImg<T>(_width,_height,_depth,_spectrum,expression,true);
+      }
+      cimg::exception_mode() = omode;
+      return *this;
+    }
+
+    //! In-place bitwise AND operator.
+    /**
+       Similar to operator+=(const CImg<t>&), except that it performs a bitwise AND operation instead of an addition.
+    **/
+    template<typename t>
+    CImg<T>& operator&=(const CImg<t>& img) {
+      const unsigned int siz = size(), isiz = img.size();
+      if (siz && isiz) {
+        if (is_overlapped(img)) return *this&=+img;
+        T *ptrd = _data, *const ptre = _data + siz;
+        if (siz>isiz) for (unsigned int n = siz/isiz; n; --n)
+          for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs<ptrs_end; ++ptrd) *ptrd = (T)((unsigned long)*ptrd & (unsigned long)*(ptrs++));
+        for (const t *ptrs = img._data; ptrd<ptre; ++ptrd) *ptrd = (T)((unsigned long)*ptrd & (unsigned long)*(ptrs++));
+      }
+      return *this;
+    }
+
+    //! Bitwise AND operator.
+    /**
+       Similar to operator&=(const t), except that it returns a new image instance instead of operating in-place.
+       The pixel type of the returned image is \c T.
+    **/
+    template<typename t>
+    CImg<T> operator&(const t value) const {
+      return (+*this)&=value;
+    }
+
+    //! Bitwise AND operator.
+    /**
+       Similar to operator&=(const char*), except that it returns a new image instance instead of operating in-place.
+       The pixel type of the returned image is \c T.
+    **/
+    CImg<T> operator&(const char *const expression) const {
+      return (+*this)&=expression;
+    }
+
+    //! Bitwise AND operator.
+    /**
+       Similar to operator&=(const CImg<t>&), except that it returns a new image instance instead of operating in-place.
+       The pixel type of the returned image is \c T.
+    **/
+    template<typename t>
+    CImg<T> operator&(const CImg<t>& img) const {
+      return (+*this)&=img;
+    }
+
+    //! In-place bitwise OR operator.
+    /**
+       Similar to operator+=(const t), except that it performs a bitwise OR operation instead of an addition.
+    **/
+    template<typename t>
+    CImg<T>& operator|=(const t value) {
+      cimg_for(*this,ptrd,T) *ptrd = (T)((unsigned long)*ptrd | (unsigned long)value);
+      return *this;
+    }
+
+    //! In-place bitwise OR operator.
+    /**
+       Similar to operator+=(const char*), except that it performs a bitwise OR operation instead of an addition.
+    **/
+    CImg<T>& operator|=(const char *const expression) {
+      const unsigned int omode = cimg::exception_mode();
+      cimg::exception_mode() = 0;
+      try {
+        const CImg<T> _base = std::strstr(expression,"i(")?+*this:CImg<T>(), &base = _base?_base:*this;
+        _cimg_math_parser mp(base,expression,"operator|=");
+        T *ptrd = _data;
+        cimg_forXYZC(*this,x,y,z,c) { *ptrd = (T)((unsigned long)*ptrd | (unsigned long)mp.eval(x,y,z,c)); ++ptrd; }
+      } catch (CImgException&) {
+        cimg::exception_mode() = omode;
+        *this|=CImg<T>(_width,_height,_depth,_spectrum,expression,true);
+      }
+      cimg::exception_mode() = omode;
+      return *this;
+    }
+
+    //! In-place bitwise OR operator.
+    /**
+       Similar to operator+=(const CImg<t>&), except that it performs a bitwise OR operation instead of an addition.
+    **/
+    template<typename t>
+    CImg<T>& operator|=(const CImg<t>& img) {
+      const unsigned int siz = size(), isiz = img.size();
+      if (siz && isiz) {
+        if (is_overlapped(img)) return *this|=+img;
+        T *ptrd = _data, *const ptre = _data + siz;
+        if (siz>isiz) for (unsigned int n = siz/isiz; n; --n)
+          for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs<ptrs_end; ++ptrd) *ptrd = (T)((unsigned long)*ptrd | (unsigned long)*(ptrs++));
+        for (const t *ptrs = img._data; ptrd<ptre; ++ptrd) *ptrd = (T)((unsigned long)*ptrd | (unsigned long)*(ptrs++));
+      }
+      return *this;
+    }
+
+    //! Bitwise OR operator.
+    /**
+       Similar to operator|=(const t), except that it returns a new image instance instead of operating in-place.
+       The pixel type of the returned image is \c T.
+    **/
+    template<typename t>
+    CImg<T> operator|(const t value) const {
+      return (+*this)|=value;
+    }
+
+    //! Bitwise OR operator.
+    /**
+       Similar to operator|=(const char*), except that it returns a new image instance instead of operating in-place.
+       The pixel type of the returned image is \c T.
+    **/
+    CImg<T> operator|(const char *const expression) const {
+      return (+*this)|=expression;
+    }
+
+    //! Bitwise OR operator.
+    /**
+       Similar to operator|=(const CImg<t>&), except that it returns a new image instance instead of operating in-place.
+       The pixel type of the returned image is \c T.
+    **/
+    template<typename t>
+    CImg<T> operator|(const CImg<t>& img) const {
+      return (+*this)|=img;
+    }
+
+    //! In-place bitwise XOR operator.
+    /**
+       Similar to operator+=(const t), except that it performs a bitwise XOR operation instead of an addition.
+       \warning
+       - It does \e not compute the \e power of pixel values. For this purpose, use pow(const t) instead.
+    **/
+    template<typename t>
+    CImg<T>& operator^=(const t value) {
+      cimg_for(*this,ptrd,T) *ptrd = (T)((unsigned long)*ptrd ^ (unsigned long)value);
+      return *this;
+    }
+
+    //! In-place bitwise XOR operator.
+    /**
+       Similar to operator+=(const char*), except that it performs a bitwise XOR operation instead of an addition.
+       \warning
+       - It does \e not compute the \e power of pixel values. For this purpose, use pow(const char*) instead.
+    **/
+    CImg<T>& operator^=(const char *const expression) {
+      const unsigned int omode = cimg::exception_mode();
+      cimg::exception_mode() = 0;
+      try {
+        const CImg<T> _base = std::strstr(expression,"i(")?+*this:CImg<T>(), &base = _base?_base:*this;
+        _cimg_math_parser mp(base,expression,"operator^=");
+        T *ptrd = _data;
+        cimg_forXYZC(*this,x,y,z,c) { *ptrd = (T)((unsigned long)*ptrd ^ (unsigned long)mp.eval(x,y,z,c)); ++ptrd; }
+      } catch (CImgException&) {
+        cimg::exception_mode() = omode;
+        *this^=CImg<T>(_width,_height,_depth,_spectrum,expression,true);
+      }
+      cimg::exception_mode() = omode;
+      return *this;
+    }
+
+    //! In-place bitwise XOR operator.
+    /**
+       Similar to operator+=(const CImg<t>&), except that it performs a bitwise XOR operation instead of an addition.
+       \warning
+       - It does \e not compute the \e power of pixel values. For this purpose, use pow(const CImg<t>&) instead.
+    **/
+    template<typename t>
+    CImg<T>& operator^=(const CImg<t>& img) {
+      const unsigned int siz = size(), isiz = img.size();
+      if (siz && isiz) {
+        if (is_overlapped(img)) return *this^=+img;
+        T *ptrd = _data, *const ptre = _data + siz;
+        if (siz>isiz) for (unsigned int n = siz/isiz; n; --n)
+          for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs<ptrs_end; ++ptrd) *ptrd = (T)((unsigned long)*ptrd ^ (unsigned long)*(ptrs++));
+        for (const t *ptrs = img._data; ptrd<ptre; ++ptrd) *ptrd = (T)((unsigned long)*ptrd ^ (unsigned long)*(ptrs++));
+      }
+      return *this;
+    }
+
+    //! Bitwise XOR operator.
+    /**
+       Similar to operator^=(const t), except that it returns a new image instance instead of operating in-place.
+       The pixel type of the returned image is \c T.
+    **/
+    template<typename t>
+    CImg<T> operator^(const t value) const {
+      return (+*this)^=value;
+    }
+
+    //! Bitwise XOR operator.
+    /**
+       Similar to operator^=(const char*), except that it returns a new image instance instead of operating in-place.
+       The pixel type of the returned image is \c T.
+    **/
+    CImg<T> operator^(const char *const expression) const {
+      return (+*this)^=expression;
+    }
+
+    //! Bitwise XOR operator.
+    /**
+       Similar to operator^=(const CImg<t>&), except that it returns a new image instance instead of operating in-place.
+       The pixel type of the returned image is \c T.
+    **/
+    template<typename t>
+    CImg<T> operator^(const CImg<t>& img) const {
+      return (+*this)^=img;
+    }
+
+    //! In-place bitwise left shift operator.
+    /**
+       Similar to operator+=(const t), except that it performs a bitwise left shift instead of an addition.
+    **/
+    template<typename t>
+    CImg<T>& operator<<=(const t value) {
+      cimg_for(*this,ptrd,T) *ptrd = (T)(((long)*ptrd) << (int)value);
+      return *this;
+    }
+
+    //! In-place bitwise left shift operator.
+    /**
+       Similar to operator+=(const char*), except that it performs a bitwise left shift instead of an addition.
+    **/
+    CImg<T>& operator<<=(const char *const expression) {
+      const unsigned int omode = cimg::exception_mode();
+      cimg::exception_mode() = 0;
+      try {
+        const CImg<T> _base = std::strstr(expression,"i(")?+*this:CImg<T>(), &base = _base?_base:*this;
+        _cimg_math_parser mp(base,expression,"operator<<=");
+        T *ptrd = _data;
+        cimg_forXYZC(*this,x,y,z,c) { *ptrd = (T)((long)*ptrd << (int)mp.eval(x,y,z,c)); ++ptrd; }
+      } catch (CImgException&) {
+        cimg::exception_mode() = omode;
+        *this<<=CImg<T>(_width,_height,_depth,_spectrum,expression,true);
+      }
+      cimg::exception_mode() = omode;
+      return *this;
+    }
+
+    //! In-place bitwise left shift operator.
+    /**
+       Similar to operator+=(const CImg<t>&), except that it performs a bitwise left shift instead of an addition.
+    **/
+    template<typename t>
+    CImg<T>& operator<<=(const CImg<t>& img) {
+      const unsigned int siz = size(), isiz = img.size();
+      if (siz && isiz) {
+        if (is_overlapped(img)) return *this^=+img;
+        T *ptrd = _data, *const ptre = _data + siz;
+        if (siz>isiz) for (unsigned int n = siz/isiz; n; --n)
+          for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs<ptrs_end; ++ptrd) *ptrd = (T)((long)*ptrd << (int)*(ptrs++));
+        for (const t *ptrs = img._data; ptrd<ptre; ++ptrd) *ptrd = (T)((long)*ptrd << (int)*(ptrs++));
+      }
+      return *this;
+    }
+
+    //! Bitwise left shift operator.
+    /**
+       Similar to operator<<=(const t), except that it returns a new image instance instead of operating in-place.
+       The pixel type of the returned image is \c T.
+    **/
+    template<typename t>
+    CImg<T> operator<<(const t value) const {
+      return (+*this)<<=value;
+    }
+
+    //! Bitwise left shift operator.
+    /**
+       Similar to operator<<=(const char*), except that it returns a new image instance instead of operating in-place.
+       The pixel type of the returned image is \c T.
+    **/
+    CImg<T> operator<<(const char *const expression) const {
+      return (+*this)<<=expression;
+    }
+
+    //! Bitwise left shift operator.
+    /**
+       Similar to operator<<=(const CImg<t>&), except that it returns a new image instance instead of operating in-place.
+       The pixel type of the returned image is \c T.
+    **/
+    template<typename t>
+    CImg<T> operator<<(const CImg<t>& img) const {
+      return (+*this)<<=img;
+    }
+
+    //! In-place bitwise right shift operator.
+    /**
+       Similar to operator+=(const t), except that it performs a bitwise right shift instead of an addition.
+    **/
+    template<typename t>
+    CImg<T>& operator>>=(const t value) {
+      cimg_for(*this,ptrd,T) *ptrd = (T)(((long)*ptrd) >> (int)value);
+      return *this;
+    }
+
+    //! In-place bitwise right shift operator.
+    /**
+       Similar to operator+=(const char*), except that it performs a bitwise right shift instead of an addition.
+    **/
+    CImg<T>& operator>>=(const char *const expression) {
+      const unsigned int omode = cimg::exception_mode();
+      cimg::exception_mode() = 0;
+      try {
+        const CImg<T> _base = std::strstr(expression,"i(")?+*this:CImg<T>(), &base = _base?_base:*this;
+        _cimg_math_parser mp(base,expression,"operator<<=");
+        T *ptrd = _data;
+        cimg_forXYZC(*this,x,y,z,c) { *ptrd = (T)((long)*ptrd >> (int)mp.eval(x,y,z,c)); ++ptrd; }
+      } catch (CImgException&) {
+        cimg::exception_mode() = omode;
+        *this>>=CImg<T>(_width,_height,_depth,_spectrum,expression,true);
+      }
+      cimg::exception_mode() = omode;
+      return *this;
+    }
+
+    //! In-place bitwise right shift operator.
+    /**
+       Similar to operator+=(const CImg<t>&), except that it performs a bitwise right shift instead of an addition.
+    **/
+    template<typename t>
+    CImg<T>& operator>>=(const CImg<t>& img) {
+      const unsigned int siz = size(), isiz = img.size();
+      if (siz && isiz) {
+        if (is_overlapped(img)) return *this^=+img;
+        T *ptrd = _data, *const ptre = _data + siz;
+        if (siz>isiz) for (unsigned int n = siz/isiz; n; --n)
+          for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs<ptrs_end; ++ptrd) *ptrd = (T)((long)*ptrd >> (int)*(ptrs++));
+        for (const t *ptrs = img._data; ptrd<ptre; ++ptrd) *ptrd = (T)((long)*ptrd >> (int)*(ptrs++));
+      }
+      return *this;
+    }
+
+    //! Bitwise right shift operator.
+    /**
+       Similar to operator>>=(const t), except that it returns a new image instance instead of operating in-place.
+       The pixel type of the returned image is \c T.
+    **/
+    template<typename t>
+    CImg<T> operator>>(const t value) const {
+      return (+*this)>>=value;
+    }
+
+    //! Bitwise right shift operator.
+    /**
+       Similar to operator>>=(const char*), except that it returns a new image instance instead of operating in-place.
+       The pixel type of the returned image is \c T.
+    **/
+    CImg<T> operator>>(const char *const expression) const {
+      return (+*this)>>=expression;
+    }
+
+    //! Bitwise right shift operator.
+    /**
+       Similar to operator>>=(const CImg<t>&), except that it returns a new image instance instead of operating in-place.
+       The pixel type of the returned image is \c T.
+    **/
+    template<typename t>
+    CImg<T> operator>>(const CImg<t>& img) const {
+      return (+*this)>>=img;
+    }
+
+    //! Bitwise inversion operator.
+    /**
+       Similar to operator-(), except that it compute the bitwise inverse instead of the opposite value.
+    **/
+    CImg<T> operator~() const {
+      CImg<T> res(_width,_height,_depth,_spectrum);
+      const T *ptrs = end();
+      cimg_for(res,ptrd,T) { const unsigned long value = (unsigned long)*(--ptrs); *ptrd = (T)~value; }
+      return res;
+    }
+
+    //! Test if all pixels of an image have the same value.
+    /**
+       Return \c true is all pixels of the image instance are equal to the specified \c value.
+       \param value : Reference value to compare with.
+       \see operator==(const char *const) const,
+           operator==(const CImg<t>&) const,
+           operator!=(const T) const,
+    **/
+    template<typename t>
+    bool operator==(const t value) const {
+      if (is_empty()) return false;
+      typedef _cimg_Tt Tt;
+      bool is_equal = true;
+      for (T *ptrd = _data + size(); is_equal && ptrd>_data; is_equal = ((Tt)*(--ptrd)==(Tt)value)) {}
+      return is_equal;
+    }
+
+    //! Test if all pixel values of an image follow a specified expression.
+    /**
+       Return \c true is all pixels of the image instance are equal to the specified \c expression.
+       \param expression : Value string describing the way pixel values are compared.
+       \see operator==(const char *const) const,
+           operator==(const CImg<t>&) const,
+           operator!=(const T) const,
+    **/
+    bool operator==(const char *const expression) const {
+      if (is_empty()) return !*expression;
+      const unsigned int omode = cimg::exception_mode();
+      cimg::exception_mode() = 0;
+      bool is_equal = true;
+      try {
+        const CImg<T> _base = std::strstr(expression,"i(")?+*this:CImg<T>(), &base = _base?_base:*this;
+        _cimg_math_parser mp(base,expression,"operator<<=");
+        const T *ptrs = _data;
+        cimg_forXYZC(*this,x,y,z,c) { if (!is_equal) break; is_equal = ((double)*(ptrs++)==mp.eval(x,y,z,c)); }
+      } catch (CImgException&) {
+        cimg::exception_mode() = omode;
+        is_equal = (*this==CImg<T>(_width,_height,_depth,_spectrum,expression,true));
+      }
+      cimg::exception_mode() = omode;
+      return is_equal;
+    }
+
+    //! Test if two images have the same size and values.
+    /**
+       Return \c true if the image instance and the input image \c img have the same dimensions and pixel values, and \c false otherwise.
+       \param img : Input image to compare with.
+       \note
+       - The pixel buffer pointers data() of the two compared images do not have to be the same for operator==() to return \c true.
+         Only the dimensions and the pixel values matter. Thus, the comparison can be \c true even for different pixel types \c T and \c t.
+       \par Example
+       \code
+       const CImg<float> img1(1,3,1,1, 0,1,2); // Construct a 1x3 vector [0;1;2] (with 'float' pixel values).
+       const CImg<char> img2(1,3,1,1, 0,1,2);  // Construct a 1x3 vector [0;1;2] (with 'char' pixel values).
+       if (img1==img2) {                       // Test succeeds, image dimensions and values are the same.
+         std::printf("'img1' and 'img2' have same dimensions and values.");
+       }
+       \endcode
+       \see operator!=(const CImg<t>&) const.
+    **/
+    template<typename t>
+    bool operator==(const CImg<t>& img) const {
+      typedef _cimg_Tt Tt;
+      const unsigned int siz = size();
+      bool is_equal = true;
+      if (siz!=img.size()) return false;
+      t *ptrs = img._data + siz;
+      for (T *ptrd = _data + siz; is_equal && ptrd>_data; is_equal = ((Tt)*(--ptrd)==(Tt)*(--ptrs))) {}
+      return is_equal;
+    }
+
+    //! Test if pixels of an image are all different from a value.
+    /**
+       Return \c true is all pixels of the image instance are different than the specified \c value.
+       \param value : Reference value to compare with.
+       \see operator==(const T) const,
+           operator!=(const CImg<t>&) const.
+    **/
+    template<typename t>
+    bool operator!=(const t value) const {
+      return !((*this)==value);
+    }
+
+    //! Test if all pixel values of an image are different from a specified expression.
+    /**
+       Return \c true is all pixels of the image instance are different to the specified \c expression.
+       \param expression : Value string describing the way pixel values are compared.
+       \see operator==(const char *const) const,
+           operator==(const CImg<t>&) const,
+           operator!=(const T) const,
+    **/
+    bool operator!=(const char *const expression) const {
+      return !((*this)==expression);
+    }
+
+    //! Test if two images have different sizes or values.
+    /**
+       Return \c true if the image instance and the input image \c img have different dimensions or pixel values, and \c false otherwise.
+       \param img : input image to compare with.
+       \note
+       - Writing \c img1!=img2 is equivalent to \c !(img1==img2).
+       \see operator==().
+    **/
+    template<typename t>
+    bool operator!=(const CImg<t>& img) const {
+      return !((*this)==img);
+    }
+
+    //! Construct an image list from two images.
+    /**
+       Return a new list of image (\c CImgList instance) containing exactly two elements :
+         - A copy of the image instance, at position [\c 0].
+         - A copy of the specified image \c img, at position [\c 1].
+
+       \param img : Input image that will be the second image of the resulting list.
+       \note
+       - The family of operator,() is convenient to easily create list of images, but it is also \e quite \e slow in practice (see warning below).
+       - Constructed lists contain no shared images. If image instance or input image \c img are shared, they are
+         inserted as new non-shared copies in the resulting list.
+       - The pixel type of the returned list may be a superset of the initial pixel type \c T, if necessary.
+       \warning
+       - Pipelining operator,() \c N times will perform \c N copies of the entire content of a (growing) image list.
+         This may become very expensive in terms of speed and used memory. You should avoid using this technique to
+         build a new CImgList instance from several images, if you are seeking for performance.
+         Fast insertions of images in an image list are possible with CImgList<T>::insert(const CImg<t>&,unsigned int,bool) or
+         move_to(CImgList<t>&,unsigned int).
+       \par Example
+       \code
+       const CImg<float>
+          img1("reference.jpg"),
+          img2 = img1.get_mirror('x'),
+          img3 = img2.get_blur(5);
+       const CImgList<float> list = (img1,img2); // Create list of two elements from 'img1' and 'img2'.
+       (list,img3).display();                    // Display image list containing copies of 'img1','img2' and 'img3'.
+       \endcode
+       \image html ref_operator_comma.jpg
+       \see operator,(const CImgList<t>&) const,
+           move_to(CImgList<t>&,unsigned int).
+           CImgList<T>::insert(const CImg<t>&,unsigned int,bool).
+    **/
+    template<typename t>
+    CImgList<_cimg_Tt> operator,(const CImg<t>& img) const {
+      return CImgList<_cimg_Tt>(*this,img);
+    }
+
+    //! Construct an image list from image instance and an input image list.
+    /**
+       Return a new list of images (\c CImgList instance) containing exactly \c list.size() \c + \c 1 elements :
+         - A copy of the image instance, at position [\c 0].
+         - A copy of the specified image list \c list, from positions [\c 1] to [\c list.size()].
+
+       \param list : Input image list that will be appended to the image instance.
+       \note
+       - Similar to operator,(const CImg<t>&) const, except that it takes an image list as an argument.
+       \see operator,(const CImg<t>&) const,
+           CImgList<T>::insert(const CImgList<t>&,unsigned int,bool).
+    **/
+    template<typename t>
+    CImgList<_cimg_Tt> operator,(const CImgList<t>& list) const {
+      return CImgList<_cimg_Tt>(list,false).insert(*this,0);
+    }
+
+    //! Split image along specified axis.
+    /**
+       Return a new list of images (\c CImgList instance) containing the splitted components
+       of the instance image along the specified axis.
+       \param axis : Splitting axis (can be '\c x','\c y','\c z' or '\c c')
+       \note
+       - Similar to get_split(char,int) const, with default second argument.
+       \par Example
+       \code
+       const CImg<unsigned char> img("reference.jpg"); // Load a RGB color image.
+       const CImgList<unsigned char> list = (img<'c'); // Get a list of its three R,G,B channels.
+       (img,list).display();
+       \endcode
+       \image html ref_operator_less.jpg
+       \see get_split(char,int) const.
+    **/
+    CImgList<T> operator<(const char axis) const {
+      return get_split(axis);
+    }
+
+    //@}
+    //-------------------------------------
+    //
+    //! \name Instance Characteristics
+    //@{
+    //-------------------------------------
+
+    //! Return the type of image pixel values as a C string.
+    /**
+       Return a \c char* string containing the usual type name of the image pixel values
+       (i.e. a stringified version of the template parameter \c T).
+       \note
+       - The returned string may contain spaces (as in \c "unsigned char").
+       - If the pixel type \c T does not correspond to a registered type, the string <tt>"unknown"</tt> is returned.
+       \see value_type.
+    **/
+    static const char* pixel_type() {
+      return cimg::type<T>::string();
+    }
+
+    //! Return the number of image columns.
+    /**
+       Return the image width, i.e. the image dimension along the X-axis.
+       \note
+       - The width() of an empty image is equal to \c 0.
+       - width() is typically equal to \c 1 when considering images as \e vectors for matrix calculations.
+       - width() returns an \c int, although the image width is internally stored as an \c unsigned \c int.
+         Using an \c int is safer and prevents arithmetic traps possibly encountered when doing calculations involving
+         \c unsigned \c int variables.
+         Access to the initial \c unsigned \c int variable is possible (though not recommended) by <tt>(*this)._width</tt>.
+       \see height(),
+           depth(),
+           spectrum(),
+           size().
+    **/
+    int width() const {
+      return (int)_width;
+    }
+
+    //! Return the number of image rows.
+    /**
+       Return the image height, i.e. the image dimension along the Y-axis.
+       \note
+       - The height() of an empty image is equal to \c 0.
+       - height() returns an \c int, although the image height is internally stored as an \c unsigned \c int.
+         Using an \c int is safer and prevents arithmetic traps possibly encountered when doing calculations involving
+         \c unsigned \c int variables.
+         Access to the initial \c unsigned \c int variable is possible (though not recommended) by <tt>(*this)._height</tt>.
+       \see width(),
+           depth(),
+           spectrum(),
+           size().
+    **/
+    int height() const {
+      return (int)_height;
+    }
+
+    //! Return the number of image slices.
+    /**
+       Return the image depth, i.e. the image dimension along the Z-axis.
+       \note
+       - The depth() of an empty image is equal to \c 0.
+       - depth() is typically equal to \c 1 when considering usual 2d images. When depth()\c > \c 1, the image
+         is said to be \e volumetric.
+       - depth() returns an \c int, although the image depth is internally stored as an \c unsigned \c int.
+         Using an \c int is safer and prevents arithmetic traps possibly encountered when doing calculations involving
+         \c unsigned \c int variables.
+         Access to the initial \c unsigned \c int variable is possible (though not recommended) by <tt>(*this)._depth</tt>.
+       \see width(),
+           height(),
+           spectrum(),
+           size().
+    **/
+    int depth() const {
+      return (int)_depth;
+    }
+
+    //! Return the number of image channels.
+    /**
+       Return the number of image channels, i.e. the image dimension along the C-axis.
+       \note
+       - The spectrum() of an empty image is equal to \c 0.
+       - spectrum() is typically equal to \c 1 when considering scalar-valued images, to \c 3 for RGB-coded color images, and to
+         \c 4 for RGBA-coded color images (with alpha-channel). The number of channels of an image instance
+         is not limited. The meaning of the pixel values is not linked up to the number of channels
+         (e.g. a 4-channel image may indifferently stands for a RGBA or CMYK color image).
+       - spectrum() returns an \c int, although the image spectrum is internally stored as an \c unsigned \c int.
+         Using an \c int is safer and prevents arithmetic traps possibly encountered when doing calculations involving
+         \c unsigned \c int variables.
+         Access to the initial \c unsigned \c int variable is possible (though not recommended) by <tt>(*this)._spectrum</tt>.
+       \see width(),
+           height(),
+           depth(),
+           size().
+    **/
+    int spectrum() const {
+      return (int)_spectrum;
+    }
+
+    //! Return the total number of pixel values.
+    /**
+       Return <tt>width()*\ref height()*\ref depth()*\ref spectrum()</tt>,
+       i.e. the total number of values of type \c T in the pixel buffer of the image instance.
+       \note
+       - The size() of an empty image is equal to \c 0.
+       - The allocated memory size for a pixel buffer of a non-shared \c CImg<T> instance is equal to <tt>size()*sizeof(T)</tt>.
+       \par Example
+       \code
+       const CImg<float> img(100,100,1,3);               // Construct new 100x100 color image.
+       if (img.size()==30000)                            // Test succeeds.
+         std::printf("Pixel buffer uses %lu bytes",
+                     img.size()*sizeof(float));
+       \endcode
+       \see width(),
+           height(),
+           depth(),
+           spectrum().
+    **/
+    unsigned int size() const {
+      return _width*_height*_depth*_spectrum;
+    }
+
+    //! Return a pointer to the first pixel value.
+    /**
+       Return a \c T*, or a \c const \c T* pointer to the first value in the pixel buffer of the image instance,
+       whether the instance is \c const or not.
+       \note
+       - The data() of an empty image is equal to \c 0 (null pointer).
+       - The allocated pixel buffer for the image instance starts from \c data()
+         and goes to <tt>data()+\ref size()-1</tt> (included).
+       - To get the pointer to one particular location of the pixel buffer, use data(unsigned int,unsigned int,unsigned int,unsigned int) instead.
+       \see operator T*() const,
+           data(unsigned int,unsigned int,unsigned int,unsigned int).
+    **/
+    T* data() {
+      return _data;
+    }
+
+    //! Return a pointer to the first pixel value \const.
+    const T* data() const {
+      return _data;
+    }
+
+    //! Return a pointer to a located pixel value.
+    /**
+       Return a \c T*, or a \c const \c T* pointer to the value located at (\c x,\c y,\c z,\c c) in the pixel buffer of the image instance,
+       whether the instance is \c const or not.
+       \param x : X-coordinate of the pixel value.
+       \param y : Y-coordinate of the pixel value.
+       \param z : Z-coordinate of the pixel value.
+       \param c : C-coordinate of the pixel value.
+       \note
+       - Writing \c img.data(x,y,z,c) is equivalent to <tt>&(img(x,y,z,c))</tt>. Thus, this method has the same properties as
+         operator()(unsigned int,unsigned int,unsigned int,unsigned int).
+       \see operator()(unsigned int,unsigned int,unsigned int,unsigned int),
+           data().
+     **/
+#if cimg_verbosity>=3
+    T *data(const unsigned int x, const unsigned int y=0, const unsigned int z=0, const unsigned int c=0) {
+      const unsigned int off = (unsigned int)offset(x,y,z,c);
+      if (off>=size()) {
+        cimg::warn(_cimg_instance
+                   "data() : Invalid pointer request, at coordinates (%u,%u,%u,%u) [offset=%u].",
+                   cimg_instance,
+                   x,y,z,c,off);
+        return _data;
+      }
+      return _data + off;
+    }
+
+    //! Return a pointer to a located pixel value \const.
+    const T* data(const unsigned int x, const unsigned int y=0, const unsigned int z=0, const unsigned int c=0) const {
+      return const_cast<CImg<T>*>(this)->data(x,y,z,c);
+    }
+#else
+    T* data(const unsigned int x, const unsigned int y=0, const unsigned int z=0, const unsigned int c=0) {
+      return _data + x + y*_width + z*_width*_height + c*_width*_height*_depth;
+    }
+
+    const T* data(const unsigned int x, const unsigned int y=0, const unsigned int z=0, const unsigned int c=0) const {
+      return _data + x + y*_width + z*_width*_height + c*_width*_height*_depth;
+    }
+#endif
+
+    //! Return the offset to a located pixel value, with respect to the beginning of the pixel buffer.
+    /**
+       \param x : X-coordinate of the pixel value.
+       \param y : Y-coordinate of the pixel value.
+       \param z : Z-coordinate of the pixel value.
+       \param c : C-coordinate of the pixel value.
+       \note
+       - Writing \c img.data(x,y,z,c) is equivalent to <tt>&(img(x,y,z,c)) - img.data()</tt>.
+         Thus, this method has the same properties as operator()(unsigned int,unsigned int,unsigned int,unsigned int).
+       \par Example
+       \code
+       const CImg<float> img(100,100,1,3);      // Define a 100x100 RGB-color image.
+       const long off = img.offset(10,10,0,2);  // Get the offset of the blue value of the pixel located at (10,10).
+       const float val = img[off];              // Get the blue value of this pixel.
+       \endcode
+       \see operator()(unsigned int,unsigned int,unsigned int,unsigned int),
+           data(unsigned int,unsigned int,unsigned int,unsigned int).
+    **/
+    int offset(const int x, const int y=0, const int z=0, const int c=0) const {
+      return x + y*_width + z*_width*_height + c*_width*_height*_depth;
+    }
+
+    //! Return a CImg<T>::iterator pointing to the first pixel value.
+    /**
+       \note
+       - Equivalent to data().
+       - It has been mainly defined for compatibility with STL naming conventions.
+       \see data().
+     **/
+    iterator begin() {
+      return _data;
+    }
+
+    //! Return a CImg<T>::iterator pointing to the first value of the pixel buffer \const.
+    const_iterator begin() const {
+      return _data;
+    }
+
+    //! Return a CImg<T>::iterator pointing next to the last pixel value.
+    /**
+       \note
+       - Writing \c img.end() is equivalent to <tt>img.data() + img.size()</tt>.
+       - It has been mainly defined for compatibility with STL naming conventions.
+       \warning
+       - The returned iterator actually points to a value located \e outside the acceptable bounds of the pixel buffer. Trying
+         to read or write the content of the returned iterator will probably result in a crash. Use it mainly as an
+         strict upper bound for a CImg<T>::iterator.
+       \par Example
+       \code
+       CImg<float> img(100,100,1,3);                                     // Define a 100x100 RGB color image.
+       for (CImg<float>::iterator it = img.begin(); it<img.end(); ++it)  // 'img.end()' used here as an upper bound for the iterator.
+         *it = 0;
+       \endcode
+       \see data().
+    **/
+    iterator end() {
+      return _data + size();
+    }
+
+    //! Return a CImg<T>::iterator pointing next to the last pixel value \const.
+    const_iterator end() const {
+      return _data + size();
+    }
+
+    //! Return a reference to the first pixel value.
+    /**
+       \note
+       - Writing \c img.front() is equivalent to <tt>img[0]</tt>, or <tt>img(0,0,0,0)</tt>.
+       - It has been mainly defined for compatibility with STL naming conventions.
+       \see data(),
+           offset(),
+           begin().
+    **/
+    T& front() {
+      return *_data;
+    }
+
+    //! Return a reference to the first pixel value \const.
+    const T& front() const {
+      return *_data;
+    }
+
+    //! Return a reference to the last pixel value.
+    /**
+       \note
+       - Writing \c img.end() is equivalent to <tt>img[img.size()-1]</tt>, or
+         <tt>img(img.width()-1,img.height()-1,img.depth()-1,img.spectrum()-1)</tt>.
+       - It has been mainly defined for compatibility with STL naming conventions.
+       \see data(),
+           offset(),
+           end().
+    **/
+    T& back() {
+      return *(_data + size() - 1);
+    }
+
+    //! Return a reference to the last pixel value \const.
+    const T& back() const {
+      return *(_data + size() - 1);
+    }
+
+    //! Access to a pixel value at a specified offset, using Dirichlet boundary conditions.
+    /**
+       Return a reference to the pixel value of the image instance located at a specified \c offset,
+       or to a specified default value in case of out-of-bounds access.
+       \param offset : Offset to the desired pixel value.
+       \param out_value : Default value returned if \c offset is outside image bounds.
+       \note
+       - Writing \c img.at(offset,out_value) is similar to <tt>img[offset]</tt>, except that if \c offset
+         is outside bounds (e.g. \c offset<0 or \c offset>=img.size()), a reference to a value \c out_value
+         is safely returned instead.
+       - Due to the additional boundary checking operation, this method is slower than operator()(). Use it when
+         you are \e not sure about the validity of the specified pixel offset.
+       \see operator()(),
+           offset(),
+           at(int).
+    **/
+    T& at(const int offset, const T out_value) {
+      return (offset<0 || offset>=(int)size())?(cimg::temporary(out_value)=out_value):(*this)[offset];
+    }
+
+    //! Access to a pixel value at a specified offset, using Dirichlet boundary conditions \const.
+    T at(const int offset, const T out_value) const {
+      return (offset<0 || offset>=(int)size())?out_value:(*this)[offset];
+    }
+
+    //! Access to a pixel value at a specified offset, using Neumann boundary conditions.
+    /**
+       Return a reference to the pixel value of the image instance located at a specified \c offset,
+       or to the nearest pixel location in the image instance in case of out-of-bounds access.
+       \param offset : Offset to the desired pixel value.
+       \note
+       - Similar to at(int,const T), except that an out-of-bounds access returns the value of the
+         nearest pixel in the image instance, regarding the specified offset, i.e.
+         - If \c offset<0, then \c img[0] is returned.
+         - If \c offset>=img.size(), then \c img[img.size()-1] is returned.
+       - Due to the additional boundary checking operation, this method is slower than operator()(). Use it when
+         you are \e not sure about the validity of the specified pixel offset.
+       - If you know your image instance is \e not empty, you may rather use the slightly faster method \c _at(int).
+       \see operator()(),
+           offset(),
+           at(int,const T).
+     **/
+    T& at(const int offset) {
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "at() : Empty instance.",
+                                    cimg_instance);
+      return _at(offset);
+    }
+
+    T& _at(const int offset) {
+      const unsigned int siz = (unsigned int)size();
+      return (*this)[offset<0?0:(unsigned int)offset>=siz?siz-1:offset];
+    }
+
+    //! Access to a pixel value at a specified offset, using Neumann boundary conditions \const.
+    T at(const int offset) const {
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "at() : Empty instance.",
+                                    cimg_instance);
+      return _at(offset);
+    }
+
+    T _at(const int offset) const {
+      const unsigned int siz = (unsigned int)size();
+      return (*this)[offset<0?0:(unsigned int)offset>=siz?siz-1:offset];
+    }
+
+    //! Access to a pixel value, using Dirichlet boundary conditions for the X-coordinate.
+    /**
+       Return a reference to the pixel value of the image instance located at (\c x,\c y,\c z,\c c),
+       or to a specified default value in case of out-of-bounds access along the X-axis.
+       \param x : X-coordinate of the pixel value.
+       \param y : Y-coordinate of the pixel value.
+       \param z : Z-coordinate of the pixel value.
+       \param c : C-coordinate of the pixel value.
+       \param out_value : Default value returned if \c (\c x,\c y,\c z,\c c) is outside image bounds.
+       \note
+       - Similar to operator()(), except that an out-of-bounds access along the X-axis returns the specified value \c out_value.
+       - Due to the additional boundary checking operation, this method is slower than operator()(). Use it when
+         you are \e not sure about the validity of the specified pixel coordinates.
+       \warning
+       - There is \e no boundary checking performed for the Y,Z and C-coordinates, so they must be inside image bounds.
+       \see operator()(),
+           at(int,const T).
+           atX(int,int,int,int),
+           atXY(int,int,int,int,const T),
+           atXYZ(int,int,int,int,const T),
+           atXYZC(int,int,int,int,const T).
+    **/
+    T& atX(const int x, const int y, const int z, const int c, const T out_value) {
+      return (x<0 || x>=width())?(cimg::temporary(out_value)=out_value):(*this)(x,y,z,c);
+    }
+
+    //! Access to a pixel value, using Dirichlet boundary conditions for the X-coordinate \const.
+    T atX(const int x, const int y, const int z, const int c, const T out_value) const {
+      return (x<0 || x>=width())?out_value:(*this)(x,y,z,c);
+    }
+
+    //! Access to a pixel value, using Neumann boundary conditions for the X-coordinate.
+    /**
+       Return a reference to the pixel value of the image instance located at (\c x,\c y,\c z,\c c),
+       or to the nearest pixel location in the image instance in case of out-of-bounds access along the X-axis.
+       \param x : X-coordinate of the pixel value.
+       \param y : Y-coordinate of the pixel value.
+       \param z : Z-coordinate of the pixel value.
+       \param c : C-coordinate of the pixel value.
+       \note
+       - Similar to at(int,int,int,int,const T), except that an out-of-bounds access returns the value of the
+         nearest pixel in the image instance, regarding the specified X-coordinate.
+       - Due to the additional boundary checking operation, this method is slower than operator()(). Use it when
+         you are \e not sure about the validity of the specified pixel coordinates.
+       - If you know your image instance is \e not empty, you may rather use the slightly faster method \c _at(int,int,int,int).
+       \warning
+       - There is \e no boundary checking performed for the Y,Z and C-coordinates, so they must be inside image bounds.
+       \see operator()(),
+           at(int),
+           atX(int,int,int,int,const T),
+           atXY(int,int,int,int),
+           atXYZ(int,int,int,int),
+           atXYZC(int,int,int,int).
+     **/
+    T& atX(const int x, const int y=0, const int z=0, const int c=0) {
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "atX() : Empty instance.",
+                                    cimg_instance);
+      return _atX(x,y,z,c);
+    }
+
+    T& _atX(const int x, const int y=0, const int z=0, const int c=0) {
+      return (*this)(x<0?0:(x>=width()?width()-1:x),y,z,c);
+    }
+
+    //! Access to a pixel value, using Neumann boundary conditions for the X-coordinate \const.
+    T atX(const int x, const int y=0, const int z=0, const int c=0) const {
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "atX() : Empty instance.",
+                                    cimg_instance);
+      return _atX(x,y,z,c);
+    }
+
+    T _atX(const int x, const int y=0, const int z=0, const int c=0) const {
+      return (*this)(x<0?0:(x>=width()?width()-1:x),y,z,c);
+    }
+
+    //! Access to a pixel value, using Dirichlet boundary conditions for the X and Y-coordinates.
+    /**
+       Similar to atX(int,int,int,int,const T), except that boundary checking is performed both on X and Y-coordinates.
+    **/
+    T& atXY(const int x, const int y, const int z, const int c, const T out_value) {
+      return (x<0 || y<0 || x>=width() || y>=height())?(cimg::temporary(out_value)=out_value):(*this)(x,y,z,c);
+    }
+
+    //! Access to a pixel value, using Dirichlet boundary conditions for the X and Y coordinates \const.
+    T atXY(const int x, const int y, const int z, const int c, const T out_value) const {
+      return (x<0 || y<0 || x>=width() || y>=height())?out_value:(*this)(x,y,z,c);
+    }
+
+    //! Access to a pixel value, using Neumann boundary conditions for the X and Y-coordinates.
+    /**
+       Similar to atX(int,int,int,int), except that boundary checking is performed both on X and Y-coordinates.
+       \note
+       - If you know your image instance is \e not empty, you may rather use the slightly faster method \c _atXY(int,int,int,int).
+     **/
+    T& atXY(const int x, const int y, const int z=0, const int c=0) {
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "atXY() : Empty instance.",
+                                    cimg_instance);
+      return _atXY(x,y,z,c);
+    }
+
+    T& _atXY(const int x, const int y, const int z=0, const int c=0) {
+      return (*this)(x<0?0:(x>=width()?width()-1:x), y<0?0:(y>=height()?height()-1:y),z,c);
+    }
+
+    //! Access to a pixel value, using Neumann boundary conditions for the X and Y-coordinates \const.
+    T atXY(const int x, const int y, const int z=0, const int c=0) const {
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "atXY() : Empty instance.",
+                                    cimg_instance);
+      return _atXY(x,y,z,c);
+    }
+
+    T _atXY(const int x, const int y, const int z=0, const int c=0) const {
+      return (*this)(x<0?0:(x>=width()?width()-1:x), y<0?0:(y>=height()?height()-1:y),z,c);
+    }
+
+    //! Access to a pixel value, using Dirichlet boundary conditions for the X,Y and Z-coordinates.
+    /**
+       Similar to atX(int,int,int,int,const T), except that boundary checking is performed both on X,Y and Z-coordinates.
+    **/
+    T& atXYZ(const int x, const int y, const int z, const int c, const T out_value) {
+      return (x<0 || y<0 || z<0 || x>=width() || y>=height() || z>=depth())?
+        (cimg::temporary(out_value)=out_value):(*this)(x,y,z,c);
+    }
+
+    //! Access to a pixel value, using Dirichlet boundary conditions for the X,Y and Z-coordinates \const.
+    T atXYZ(const int x, const int y, const int z, const int c, const T out_value) const {
+      return (x<0 || y<0 || z<0 || x>=width() || y>=height() || z>=depth())?out_value:(*this)(x,y,z,c);
+    }
+
+    //! Access to a pixel value, using Neumann boundary conditions for the X,Y and Z-coordinates.
+    /**
+       Similar to atX(int,int,int,int), except that boundary checking is performed both on X,Y and Z-coordinates.
+       \note
+       - If you know your image instance is \e not empty, you may rather use the slightly faster method \c _atXYZ(int,int,int,int).
+    **/
+    T& atXYZ(const int x, const int y, const int z, const int c=0) {
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "atXYZ() : Empty instance.",
+                                    cimg_instance);
+      return _atXYZ(x,y,z,c);
+    }
+
+    T& _atXYZ(const int x, const int y, const int z, const int c=0) {
+      return (*this)(x<0?0:(x>=width()?width()-1:x),y<0?0:(y>=height()?height()-1:y),
+                     z<0?0:(z>=depth()?depth()-1:z),c);
+    }
+
+    //! Access to a pixel value, using Neumann boundary conditions for the X,Y and Z-coordinates \const.
+    T atXYZ(const int x, const int y, const int z, const int c=0) const {
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "atXYZ() : Empty instance.",
+                                    cimg_instance);
+      return _atXYZ(x,y,z,c);
+    }
+
+    T _atXYZ(const int x, const int y, const int z, const int c=0) const {
+      return (*this)(x<0?0:(x>=width()?width()-1:x),y<0?0:(y>=height()?height()-1:y),
+                     z<0?0:(z>=depth()?depth()-1:z),c);
+    }
+
+    //! Access to a pixel value, using Dirichlet boundary conditions.
+    /**
+       Similar to atX(int,int,int,int,const T), except that boundary checking is performed on all X,Y,Z and C-coordinates.
+    **/
+    T& atXYZC(const int x, const int y, const int z, const int c, const T out_value) {
+      return (x<0 || y<0 || z<0 || c<0 || x>=width() || y>=height() || z>=depth() || c>=spectrum())?
+        (cimg::temporary(out_value)=out_value):(*this)(x,y,z,c);
+    }
+
+    //! Access to a pixel value, using Dirichlet boundary conditions \const.
+    T atXYZC(const int x, const int y, const int z, const int c, const T out_value) const {
+      return (x<0 || y<0 || z<0 || c<0 || x>=width() || y>=height() || z>=depth() || c>=spectrum())?out_value:(*this)(x,y,z,c);
+    }
+
+    //! Access to a pixel value, using Neumann boundary conditions.
+    /**
+       Similar to atX(int,int,int,int), except that boundary checking is performed on all X,Y,Z and C-coordinates.
+       \note
+       - If you know your image instance is \e not empty, you may rather use the slightly faster method \c _atXYZC(int,int,int,int).
+    **/
+    T& atXYZC(const int x, const int y, const int z, const int c) {
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "atXYZC() : Empty instance.",
+                                    cimg_instance);
+      return _atXYZC(x,y,z,c);
+    }
+
+    T& _atXYZC(const int x, const int y, const int z, const int c) {
+      return (*this)(x<0?0:(x>=width()?width()-1:x), y<0?0:(y>=height()?height()-1:y),
+                     z<0?0:(z>=depth()?depth()-1:z), c<0?0:(c>=spectrum()?spectrum()-1:c));
+    }
+
+    //! Access to a pixel value, using Neumann boundary conditions \const.
+    T atXYZC(const int x, const int y, const int z, const int c) const {
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "atXYZC() : Empty instance.",
+                                    cimg_instance);
+      return _atXYZC(x,y,z,c);
+    }
+
+    T _atXYZC(const int x, const int y, const int z, const int c) const {
+      return (*this)(x<0?0:(x>=width()?width()-1:x), y<0?0:(y>=height()?height()-1:y),
+                     z<0?0:(z>=depth()?depth()-1:z), c<0?0:(c>=spectrum()?spectrum()-1:c));
+    }
+
+    //! Return pixel value, using linear interpolation and Dirichlet boundary conditions for the X-coordinate.
+    /**
+       Return a linearly-interpolated pixel value of the image instance located at (\c fx,\c y,\c z,\c c),
+       or a specified default value in case of out-of-bounds access along the X-axis.
+       \param fx : X-coordinate of the pixel value (float-valued).
+       \param y : Y-coordinate of the pixel value.
+       \param z : Z-coordinate of the pixel value.
+       \param c : C-coordinate of the pixel value.
+       \param out_value : Default value returned if \c (\c fx,\c y,\c z,\c c) is outside image bounds.
+       \note
+       - Similar to atX(int,int,int,int,const T), except that the returned pixel value is approximated by a linear interpolation along the X-axis,
+         if corresponding coordinates are not integers.
+       - The type of the returned pixel value is extended to \c float, if the pixel type \c T is not float-valued.
+       \warning
+       - There is \e no boundary checking performed for the Y,Z and C-coordinates, so they must be inside image bounds.
+       \see operator()(),
+           atX(int,int,int,int,const T),
+           linear_atX(float,int,int,int) const,
+           linear_atXY(float,float,int,int,const T) const,
+           linear_atXYZ(float,float,float,int,const T) const,
+           linear_atXYZC(float,float,float,float,const T) const.
+    **/
+    Tfloat linear_atX(const float fx, const int y, const int z, const int c, const T out_value) 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,c,out_value), In = (Tfloat)atXY(nx,y,z,c,out_value);
+      return Ic + dx*(In-Ic);
+    }
+
+    //! Return pixel value, using linear interpolation and Neumann boundary conditions for the X-coordinate.
+    /**
+       Return a linearly-interpolated pixel value of the image instance located at (\c fx,\c y,\c z,\c c),
+       or the value of the nearest pixel location in the image instance in case of out-of-bounds access along the X-axis.
+       \param fx : X-coordinate of the pixel value (float-valued).
+       \param y : Y-coordinate of the pixel value.
+       \param z : Z-coordinate of the pixel value.
+       \param c : C-coordinate of the pixel value.
+       \note
+       - Similar to linear_atX(float,int,int,int,const T) const, except that an out-of-bounds access returns the value of the
+         nearest pixel in the image instance, regarding the specified X-coordinate.
+       - If you know your image instance is \e not empty, you may rather use the slightly faster method \c _linear_atX(float,int,int,int).
+       \warning
+       - There is \e no boundary checking performed for the Y,Z and C-coordinates, so they must be inside image bounds.
+       \see operator()(),
+           atX(int,int,int,int),
+           linear_atX(float,int,int,int,const T) const,
+           linear_atXY(float,float,int,int) const,
+           linear_atXYZ(float,float,float,int) const,
+           linear_atXYZC(float,float,float,float) const.
+    **/
+    Tfloat linear_atX(const float fx, const int y=0, const int z=0, const int c=0) const {
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "linear_atX() : Empty instance.",
+                                    cimg_instance);
+
+      return _linear_atX(fx,y,z,c);
+    }
+
+    Tfloat _linear_atX(const float fx, const int y=0, const int z=0, const int c=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,c), In = (Tfloat)(*this)(nx,y,z,c);
+      return Ic + dx*(In-Ic);
+    }
+
+    //! Return pixel value, using linear interpolation and Dirichlet boundary conditions for the X and Y-coordinates.
+    /**
+       Similar to linear_atX(float,int,int,int,const T) const, except that the linear interpolation and the boundary checking
+       are achieved both for X and Y-coordinates.
+    **/
+    Tfloat linear_atXY(const float fx, const float fy, const int z, const int c, const T out_value) 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,c,out_value),  Inc = (Tfloat)atXY(nx,y,z,c,out_value),
+        Icn = (Tfloat)atXY(x,ny,z,c,out_value), Inn = (Tfloat)atXY(nx,ny,z,c,out_value);
+      return Icc + dx*(Inc-Icc + dy*(Icc+Inn-Icn-Inc)) + dy*(Icn-Icc);
+    }
+
+    //! Return pixel value, using linear interpolation and Neumann boundary conditions for the X and Y-coordinates.
+    /**
+       Similar to linear_atX(float,int,int,int) const, except that the linear interpolation and the boundary checking
+       are achieved both for X and Y-coordinates.
+       \note
+       - If you know your image instance is \e not empty, you may rather use the slightly faster method \c _linear_atXY(float,float,int,int).
+    **/
+    Tfloat linear_atXY(const float fx, const float fy, const int z=0, const int c=0) const {
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "linear_atXY() : Empty instance.",
+                                    cimg_instance);
+
+      return _linear_atXY(fx,fy,z,c);
+    }
+
+    Tfloat _linear_atXY(const float fx, const float fy, const int z=0, const int c=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,c),  Inc = (Tfloat)(*this)(nx,y,z,c),
+        Icn = (Tfloat)(*this)(x,ny,z,c), Inn = (Tfloat)(*this)(nx,ny,z,c);
+      return Icc + dx*(Inc-Icc + dy*(Icc+Inn-Icn-Inc)) + dy*(Icn-Icc);
+    }
+
+    //! Return pixel value, using linear interpolation and Dirichlet boundary conditions for the X,Y and Z-coordinates.
+    /**
+       Similar to linear_atX(float,int,int,int,const T) const, except that the linear interpolation and the boundary checking
+       are achieved both for X,Y and Z-coordinates.
+    **/
+    Tfloat linear_atXYZ(const float fx, const float fy, const float fz, const int c, const T out_value) 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,c,out_value), Incc = (Tfloat)atXYZ(nx,y,z,c,out_value),
+        Icnc = (Tfloat)atXYZ(x,ny,z,c,out_value), Innc = (Tfloat)atXYZ(nx,ny,z,c,out_value),
+        Iccn = (Tfloat)atXYZ(x,y,nz,c,out_value), Incn = (Tfloat)atXYZ(nx,y,nz,c,out_value),
+        Icnn = (Tfloat)atXYZ(x,ny,nz,c,out_value), Innn = (Tfloat)atXYZ(nx,ny,nz,c,out_value);
+      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);
+    }
+
+    //! Return pixel value, using linear interpolation and Neumann boundary conditions for the X,Y and Z-coordinates.
+    /**
+       Similar to linear_atX(float,int,int,int) const, except that the linear interpolation and the boundary checking
+       are achieved both for X,Y and Z-coordinates.
+       \note
+       - If you know your image instance is \e not empty, you may rather use the slightly faster method \c _linear_atXYZ(float,float,float,int).
+    **/
+    Tfloat linear_atXYZ(const float fx, const float fy=0, const float fz=0, const int c=0) const {
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "linear_atXYZ() : Empty instance.",
+                                    cimg_instance);
+
+      return _linear_atXYZ(fx,fy,fz,c);
+    }
+
+    Tfloat _linear_atXYZ(const float fx, const float fy=0, const float fz=0, const int c=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,c), Incc = (Tfloat)(*this)(nx,y,z,c),
+        Icnc = (Tfloat)(*this)(x,ny,z,c), Innc = (Tfloat)(*this)(nx,ny,z,c),
+        Iccn = (Tfloat)(*this)(x,y,nz,c), Incn = (Tfloat)(*this)(nx,y,nz,c),
+        Icnn = (Tfloat)(*this)(x,ny,nz,c), Innn = (Tfloat)(*this)(nx,ny,nz,c);
+      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);
+    }
+
+    //! Return pixel value, using linear interpolation and Dirichlet boundary conditions for all X,Y,Z and C-coordinates.
+    /**
+       Similar to linear_atX(float,int,int,int,const T) const, except that the linear interpolation and the boundary checking
+       are achieved for all X,Y,Z and C-coordinates.
+    **/
+    Tfloat linear_atXYZC(const float fx, const float fy, const float fz, const float fc, const T out_value) 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,
+        c = (int)fc - (fc>=0?0:1), nc = c + 1;
+      const float
+        dx = fx - x,
+        dy = fy - y,
+        dz = fz - z,
+        dc = fc - c;
+      const Tfloat
+        Icccc = (Tfloat)atXYZC(x,y,z,c,out_value), Inccc = (Tfloat)atXYZC(nx,y,z,c,out_value),
+        Icncc = (Tfloat)atXYZC(x,ny,z,c,out_value), Inncc = (Tfloat)atXYZC(nx,ny,z,c,out_value),
+        Iccnc = (Tfloat)atXYZC(x,y,nz,c,out_value), Incnc = (Tfloat)atXYZC(nx,y,nz,c,out_value),
+        Icnnc = (Tfloat)atXYZC(x,ny,nz,c,out_value), Innnc = (Tfloat)atXYZC(nx,ny,nz,c,out_value),
+        Icccn = (Tfloat)atXYZC(x,y,z,nc,out_value), Inccn = (Tfloat)atXYZC(nx,y,z,nc,out_value),
+        Icncn = (Tfloat)atXYZC(x,ny,z,nc,out_value), Inncn = (Tfloat)atXYZC(nx,ny,z,nc,out_value),
+        Iccnn = (Tfloat)atXYZC(x,y,nz,nc,out_value), Incnn = (Tfloat)atXYZC(nx,y,nz,nc,out_value),
+        Icnnn = (Tfloat)atXYZC(x,ny,nz,nc,out_value), Innnn = (Tfloat)atXYZC(nx,ny,nz,nc,out_value);
+      return Icccc +
+        dx*(Inccc-Icccc +
+            dy*(Icccc+Inncc-Icncc-Inccc +
+                dz*(Iccnc+Innnc+Icncc+Inccc-Icnnc-Incnc-Icccc-Inncc +
+                    dc*(Iccnn+Innnn+Icncn+Inccn+Icnnc+Incnc+Icccc+Inncc-Icnnn-Incnn-Icccn-Inncn-Iccnc-Innnc-Icncc-Inccc)) +
+                dc*(Icccn+Inncn+Icncc+Inccc-Icncn-Inccn-Icccc-Inncc)) +
+            dz*(Icccc+Incnc-Iccnc-Inccc +
+                dc*(Icccn+Incnn+Iccnc+Inccc-Iccnn-Inccn-Icccc-Incnc)) +
+            dc*(Icccc+Inccn-Inccc-Icccn)) +
+        dy*(Icncc-Icccc +
+            dz*(Icccc+Icnnc-Iccnc-Icncc +
+                dc*(Icccn+Icnnn+Iccnc+Icncc-Iccnn-Icncn-Icccc-Icnnc)) +
+            dc*(Icccc+Icncn-Icncc-Icccn)) +
+        dz*(Iccnc-Icccc +
+            dc*(Icccc+Iccnn-Iccnc-Icccn)) +
+        dc*(Icccn-Icccc);
+    }
+
+    //! Return pixel value, using linear interpolation and Neumann boundary conditions for all X,Y,Z and C-coordinates.
+    /**
+       Similar to linear_atX(float,int,int,int) const, except that the linear interpolation and the boundary checking
+       are achieved for all X,Y,Z and C-coordinates.
+       \note
+       - If you know your image instance is \e not empty, you may rather use the slightly faster method \c _linear_atXYZC(float,float,float,float).
+    **/
+    Tfloat linear_atXYZC(const float fx, const float fy=0, const float fz=0, const float fc=0) const {
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "linear_atXYZC() : Empty instance.",
+                                    cimg_instance);
+
+      return _linear_atXYZC(fx,fy,fz,fc);
+    }
+
+    Tfloat _linear_atXYZC(const float fx, const float fy=0, const float fz=0, const float fc=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),
+        nfc = fc<0?0:(fc>_spectrum-1?_spectrum-1:fc);
+      const unsigned int
+        x = (unsigned int)nfx,
+        y = (unsigned int)nfy,
+        z = (unsigned int)nfz,
+        c = (unsigned int)nfc;
+      const float
+        dx = nfx - x,
+        dy = nfy - y,
+        dz = nfz - z,
+        dc = nfc - c;
+      const unsigned int
+        nx = dx>0?x+1:x,
+        ny = dy>0?y+1:y,
+        nz = dz>0?z+1:z,
+        nc = dc>0?c+1:c;
+      const Tfloat
+        Icccc = (Tfloat)(*this)(x,y,z,c), Inccc = (Tfloat)(*this)(nx,y,z,c),
+        Icncc = (Tfloat)(*this)(x,ny,z,c), Inncc = (Tfloat)(*this)(nx,ny,z,c),
+        Iccnc = (Tfloat)(*this)(x,y,nz,c), Incnc = (Tfloat)(*this)(nx,y,nz,c),
+        Icnnc = (Tfloat)(*this)(x,ny,nz,c), Innnc = (Tfloat)(*this)(nx,ny,nz,c),
+        Icccn = (Tfloat)(*this)(x,y,z,nc), Inccn = (Tfloat)(*this)(nx,y,z,nc),
+        Icncn = (Tfloat)(*this)(x,ny,z,nc), Inncn = (Tfloat)(*this)(nx,ny,z,nc),
+        Iccnn = (Tfloat)(*this)(x,y,nz,nc), Incnn = (Tfloat)(*this)(nx,y,nz,nc),
+        Icnnn = (Tfloat)(*this)(x,ny,nz,nc), Innnn = (Tfloat)(*this)(nx,ny,nz,nc);
+      return Icccc +
+        dx*(Inccc-Icccc +
+            dy*(Icccc+Inncc-Icncc-Inccc +
+                dz*(Iccnc+Innnc+Icncc+Inccc-Icnnc-Incnc-Icccc-Inncc +
+                    dc*(Iccnn+Innnn+Icncn+Inccn+Icnnc+Incnc+Icccc+Inncc-Icnnn-Incnn-Icccn-Inncn-Iccnc-Innnc-Icncc-Inccc)) +
+                dc*(Icccn+Inncn+Icncc+Inccc-Icncn-Inccn-Icccc-Inncc)) +
+            dz*(Icccc+Incnc-Iccnc-Inccc +
+                dc*(Icccn+Incnn+Iccnc+Inccc-Iccnn-Inccn-Icccc-Incnc)) +
+            dc*(Icccc+Inccn-Inccc-Icccn)) +
+        dy*(Icncc-Icccc +
+            dz*(Icccc+Icnnc-Iccnc-Icncc +
+                dc*(Icccn+Icnnn+Iccnc+Icncc-Iccnn-Icncn-Icccc-Icnnc)) +
+            dc*(Icccc+Icncn-Icncc-Icccn)) +
+        dz*(Iccnc-Icccc +
+            dc*(Icccc+Iccnn-Iccnc-Icccn)) +
+        dc*(Icccn-Icccc);
+    }
+
+    //! Return pixel value, using cubic interpolation and Dirichlet boundary conditions for the X-coordinate.
+    /**
+       Return a cubicly-interpolated pixel value of the image instance located at (\c fx,\c y,\c z,\c c),
+       or a specified default value in case of out-of-bounds access along the X-axis.
+       \param fx : X-coordinate of the pixel value (float-valued).
+       \param y : Y-coordinate of the pixel value.
+       \param z : Z-coordinate of the pixel value.
+       \param c : C-coordinate of the pixel value.
+       \param out_value : Default value returned if \c (\c fx,\c y,\c z,\c c) is outside image bounds.
+       \note
+       - Similar to linear_atX(float,int,int,int,const T) const, except that the returned pixel value is approximated by a
+         \e cubic interpolation along the X-axis.
+       - The type of the returned pixel value is extended to \c float, if the pixel type \c T is not float-valued.
+       \warning
+       - There is \e no boundary checking performed for the Y,Z and C-coordinates, so they must be inside image bounds.
+       \see operator()(),
+           atX(int,int,int,int,const T),
+           linear_atX(float,int,int,int,const T) const,
+           cubic_atX(float,int,int,int) const,
+           cubic_atXY(float,float,int,int,const T) const,
+           cubic_atXYZ(float,float,float,int,const T) const.
+    **/
+    Tfloat cubic_atX(const float fx, const int y, const int z, const int c, const T out_value) 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,c,out_value), Ic = (Tfloat)atX(x,y,z,c,out_value),
+        In = (Tfloat)atX(nx,y,z,c,out_value), Ia = (Tfloat)atX(ax,y,z,c,out_value);
+      return Ic + 0.5f*(dx*(-Ip+In) + dx*dx*(2*Ip-5*Ic+4*In-Ia) + dx*dx*dx*(-Ip+3*Ic-3*In+Ia));
+    }
+
+    //! Return damped pixel value, using cubic interpolation and Dirichlet boundary conditions for the X-coordinate.
+    /**
+       Similar to cubic_atX(float,int,int,int,const T) const, except that you can specify the authorized minimum and maximum of the returned value.
+    **/
+    Tfloat cubic_atX(const float fx, const int y, const int z, const int c, const T out_value,
+                     const Tfloat min_value, const Tfloat max_value) const {
+      const Tfloat val = cubic_atX(fx,y,z,c,out_value);
+      return val<min_value?min_value:val>max_value?max_value:val;
+    }
+
+    //! Return pixel value, using cubic interpolation and Neumann boundary conditions for the X-coordinate.
+    /**
+       Return a cubicly-interpolated pixel value of the image instance located at (\c fx,\c y,\c z,\c c),
+       or the value of the nearest pixel location in the image instance in case of out-of-bounds access along the X-axis.
+       \param fx : X-coordinate of the pixel value (float-valued).
+       \param y : Y-coordinate of the pixel value.
+       \param z : Z-coordinate of the pixel value.
+       \param c : C-coordinate of the pixel value.
+       \note
+       - Similar to cubic_atX(float,int,int,int,const T) const, except that the returned pixel value is approximated by a cubic interpolation along the X-axis.
+       - If you know your image instance is \e not empty, you may rather use the slightly faster method \c _cubic_atX(float,int,int,int).
+       \warning
+       - There is \e no boundary checking performed for the Y,Z and C-coordinates, so they must be inside image bounds.
+       \see operator()(),
+           atX(int,int,int,int),
+           linear_atX(float,int,int,int) const,
+           cubic_atX(float,int,int,int,const T) const,
+           cubic_atXY(float,float,int,int) const,
+           cubic_atXYZ(float,float,float,int) const.
+    **/
+    Tfloat cubic_atX(const float fx, const int y=0, const int z=0, const int c=0) const {
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "cubic_atX() : Empty instance.",
+                                    cimg_instance);
+      return _cubic_atX(fx,y,z,c);
+    }
+
+    Tfloat _cubic_atX(const float fx, const int y=0, const int z=0, const int c=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>=width()?width()-1:x+2;
+      const Tfloat
+        Ip = (Tfloat)(*this)(px,y,z,c), Ic = (Tfloat)(*this)(x,y,z,c),
+        In = (Tfloat)(*this)(nx,y,z,c), Ia = (Tfloat)(*this)(ax,y,z,c);
+      return Ic + 0.5f*(dx*(-Ip+In) + dx*dx*(2*Ip-5*Ic+4*In-Ia) + dx*dx*dx*(-Ip+3*Ic-3*In+Ia));
+    }
+
+    //! Return damped pixel value, using cubic interpolation and Neumann boundary conditions for the X-coordinate.
+    /**
+       Similar to cubic_atX(float,int,int,int) const, except that you can specify the authorized minimum and maximum of the returned value.
+    **/
+    Tfloat cubic_atX(const float fx, const int y, const int z, const int c,
+                     const Tfloat min_value, const Tfloat max_value) const {
+      const Tfloat val = cubic_atX(fx,y,z,c);
+      return val<min_value?min_value:val>max_value?max_value:val;
+    }
+
+    Tfloat _cubic_atX(const float fx, const int y, const int z, const int c,
+                      const Tfloat min_value, const Tfloat max_value) const {
+      const Tfloat val = _cubic_atX(fx,y,z,c);
+      return val<min_value?min_value:val>max_value?max_value:val;
+    }
+
+    //! Return pixel value, using cubic interpolation and Dirichlet boundary conditions for the X and Y-coordinates.
+    /**
+       Similar to cubic_atX(float,int,int,int,const T) const, except that the cubic interpolation and boundary checking
+       are achieved both for X and Y-coordinates.
+    **/
+    Tfloat cubic_atXY(const float fx, const float fy, const int z, const int c, const T out_value) 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, dy = fy - y;
+      const Tfloat
+        Ipp = (Tfloat)atXY(px,py,z,c,out_value), Icp = (Tfloat)atXY(x,py,z,c,out_value), Inp = (Tfloat)atXY(nx,py,z,c,out_value), Iap = (Tfloat)atXY(ax,py,z,c,out_value),
+        Ip = Icp + 0.5f*(dx*(-Ipp+Inp) + dx*dx*(2*Ipp-5*Icp+4*Inp-Iap) + dx*dx*dx*(-Ipp+3*Icp-3*Inp+Iap)),
+        Ipc = (Tfloat)atXY(px,y,z,c,out_value),  Icc = (Tfloat)atXY(x, y,z,c,out_value), Inc = (Tfloat)atXY(nx,y,z,c,out_value),  Iac = (Tfloat)atXY(ax,y,z,c,out_value),
+        Ic = Icc + 0.5f*(dx*(-Ipc+Inc) + dx*dx*(2*Ipc-5*Icc+4*Inc-Iac) + dx*dx*dx*(-Ipc+3*Icc-3*Inc+Iac)),
+        Ipn = (Tfloat)atXY(px,ny,z,c,out_value), Icn = (Tfloat)atXY(x,ny,z,c,out_value), Inn = (Tfloat)atXY(nx,ny,z,c,out_value), Ian = (Tfloat)atXY(ax,ny,z,c,out_value),
+        In = Icn + 0.5f*(dx*(-Ipn+Inn) + dx*dx*(2*Ipn-5*Icn+4*Inn-Ian) + dx*dx*dx*(-Ipn+3*Icn-3*Inn+Ian)),
+        Ipa = (Tfloat)atXY(px,ay,z,c,out_value), Ica = (Tfloat)atXY(x,ay,z,c,out_value), Ina = (Tfloat)atXY(nx,ay,z,c,out_value), Iaa = (Tfloat)atXY(ax,ay,z,c,out_value),
+        Ia = Ica + 0.5f*(dx*(-Ipa+Ina) + dx*dx*(2*Ipa-5*Ica+4*Ina-Iaa) + dx*dx*dx*(-Ipa+3*Ica-3*Ina+Iaa));
+      return Ic + 0.5f*(dy*(-Ip+In) + dy*dy*(2*Ip-5*Ic+4*In-Ia) + dy*dy*dy*(-Ip+3*Ic-3*In+Ia));
+    }
+
+    //! Return damped pixel value, using cubic interpolation and Dirichlet boundary conditions for the X and Y-coordinates.
+    /**
+       Similar to cubic_atXY(float,float,int,int,const T) const, except that you can specify the authorized minimum and maximum of the returned value.
+    **/
+    Tfloat cubic_atXY(const float fx, const float fy, const int z, const int c, const T out_value,
+                      const Tfloat min_value, const Tfloat max_value) const {
+      const Tfloat val = cubic_atXY(fx,fy,z,c,out_value);
+      return val<min_value?min_value:val>max_value?max_value:val;
+    }
+
+    //! Return pixel value, using cubic interpolation and Neumann boundary conditions for the X and Y-coordinates.
+    /**
+       Similar to cubic_atX(float,int,int,int) const, except that the cubic interpolation and boundary checking
+       are achieved for both X and Y-coordinates.
+       \note
+       - If you know your image instance is \e not empty, you may rather use the slightly faster method \c _cubic_atXY(float,float,int,int).
+    **/
+    Tfloat cubic_atXY(const float fx, const float fy, const int z=0, const int c=0) const {
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "cubic_atXY() : Empty instance.",
+                                    cimg_instance);
+      return _cubic_atXY(fx,fy,z,c);
+    }
+
+    Tfloat _cubic_atXY(const float fx, const float fy, const int z=0, const int c=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, dy = nfy - y;
+      const int
+        px = x-1<0?0:x-1, nx = dx>0?x+1:x, ax = x+2>=width()?width()-1:x+2,
+        py = y-1<0?0:y-1, ny = dy>0?y+1:y, ay = y+2>=height()?height()-1:y+2;
+      const Tfloat
+        Ipp = (Tfloat)(*this)(px,py,z,c), Icp = (Tfloat)(*this)(x,py,z,c), Inp = (Tfloat)(*this)(nx,py,z,c), Iap = (Tfloat)(*this)(ax,py,z,c),
+        Ip = Icp + 0.5f*(dx*(-Ipp+Inp) + dx*dx*(2*Ipp-5*Icp+4*Inp-Iap) + dx*dx*dx*(-Ipp+3*Icp-3*Inp+Iap)),
+        Ipc = (Tfloat)(*this)(px,y,z,c),  Icc = (Tfloat)(*this)(x, y,z,c), Inc = (Tfloat)(*this)(nx,y,z,c),  Iac = (Tfloat)(*this)(ax,y,z,c),
+        Ic = Icc + 0.5f*(dx*(-Ipc+Inc) + dx*dx*(2*Ipc-5*Icc+4*Inc-Iac) + dx*dx*dx*(-Ipc+3*Icc-3*Inc+Iac)),
+        Ipn = (Tfloat)(*this)(px,ny,z,c), Icn = (Tfloat)(*this)(x,ny,z,c), Inn = (Tfloat)(*this)(nx,ny,z,c), Ian = (Tfloat)(*this)(ax,ny,z,c),
+        In = Icn + 0.5f*(dx*(-Ipn+Inn) + dx*dx*(2*Ipn-5*Icn+4*Inn-Ian) + dx*dx*dx*(-Ipn+3*Icn-3*Inn+Ian)),
+        Ipa = (Tfloat)(*this)(px,ay,z,c), Ica = (Tfloat)(*this)(x,ay,z,c), Ina = (Tfloat)(*this)(nx,ay,z,c), Iaa = (Tfloat)(*this)(ax,ay,z,c),
+        Ia = Ica + 0.5f*(dx*(-Ipa+Ina) + dx*dx*(2*Ipa-5*Ica+4*Ina-Iaa) + dx*dx*dx*(-Ipa+3*Ica-3*Ina+Iaa));
+      return Ic + 0.5f*(dy*(-Ip+In) + dy*dy*(2*Ip-5*Ic+4*In-Ia) + dy*dy*dy*(-Ip+3*Ic-3*In+Ia));
+    }
+
+    //! Return damped pixel value, using cubic interpolation and Neumann boundary conditions for the X and Y-coordinates.
+    /**
+       Similar to cubic_atXY(float,float,int,int) const, except that you can specify the authorized minimum and maximum of the returned value.
+    **/
+    Tfloat cubic_atXY(const float fx, const float fy, const int z, const int c,
+                      const Tfloat min_value, const Tfloat max_value) const {
+      const Tfloat val = cubic_atXY(fx,fy,z,c);
+      return val<min_value?min_value:val>max_value?max_value:val;
+    }
+
+    Tfloat _cubic_atXY(const float fx, const float fy, const int z, const int c,
+                       const Tfloat min_value, const Tfloat max_value) const {
+      const Tfloat val = _cubic_atXY(fx,fy,z,c);
+      return val<min_value?min_value:val>max_value?max_value:val;
+    }
+
+    //! Return pixel value, using cubic interpolation and Dirichlet boundary conditions for the X,Y and Z-coordinates.
+    /**
+       Similar to cubic_atX(float,int,int,int,const T) const, except that the cubic interpolation and boundary checking
+       are achieved both for X,Y and Z-coordinates.
+    **/
+    Tfloat cubic_atXYZ(const float fx, const float fy, const float fz, const int c, const T out_value) 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,
+        z = (int)fz - (fz>=0?0:1), pz = z - 1, nz = z + 1, az = z + 2;
+      const float dx = fx - x, dy = fy - y, dz = fz - z;
+      const Tfloat
+        Ippp = (Tfloat)atXYZ(px,py,pz,c,out_value), Icpp = (Tfloat)atXYZ(x,py,pz,c,out_value),
+        Inpp = (Tfloat)atXYZ(nx,py,pz,c,out_value), Iapp = (Tfloat)atXYZ(ax,py,pz,c,out_value),
+        Ipp = Icpp + 0.5f*(dx*(-Ippp+Inpp) + dx*dx*(2*Ippp-5*Icpp+4*Inpp-Iapp) + dx*dx*dx*(-Ippp+3*Icpp-3*Inpp+Iapp)),
+        Ipcp = (Tfloat)atXYZ(px,y,pz,c,out_value),  Iccp = (Tfloat)atXYZ(x, y,pz,c,out_value),
+        Incp = (Tfloat)atXYZ(nx,y,pz,c,out_value),  Iacp = (Tfloat)atXYZ(ax,y,pz,c,out_value),
+        Icp = Iccp + 0.5f*(dx*(-Ipcp+Incp) + dx*dx*(2*Ipcp-5*Iccp+4*Incp-Iacp) + dx*dx*dx*(-Ipcp+3*Iccp-3*Incp+Iacp)),
+        Ipnp = (Tfloat)atXYZ(px,ny,pz,c,out_value), Icnp = (Tfloat)atXYZ(x,ny,pz,c,out_value),
+        Innp = (Tfloat)atXYZ(nx,ny,pz,c,out_value), Ianp = (Tfloat)atXYZ(ax,ny,pz,c,out_value),
+        Inp = Icnp + 0.5f*(dx*(-Ipnp+Innp) + dx*dx*(2*Ipnp-5*Icnp+4*Innp-Ianp) + dx*dx*dx*(-Ipnp+3*Icnp-3*Innp+Ianp)),
+        Ipap = (Tfloat)atXYZ(px,ay,pz,c,out_value), Icap = (Tfloat)atXYZ(x,ay,pz,c,out_value),
+        Inap = (Tfloat)atXYZ(nx,ay,pz,c,out_value), Iaap = (Tfloat)atXYZ(ax,ay,pz,c,out_value),
+        Iap = Icap + 0.5f*(dx*(-Ipap+Inap) + dx*dx*(2*Ipap-5*Icap+4*Inap-Iaap) + dx*dx*dx*(-Ipap+3*Icap-3*Inap+Iaap)),
+        Ip = Icp + 0.5f*(dy*(-Ipp+Inp) + dy*dy*(2*Ipp-5*Icp+4*Inp-Iap) + dy*dy*dy*(-Ipp+3*Icp-3*Inp+Iap)),
+        Ippc = (Tfloat)atXYZ(px,py,z,c,out_value), Icpc = (Tfloat)atXYZ(x,py,z,c,out_value),
+        Inpc = (Tfloat)atXYZ(nx,py,z,c,out_value), Iapc = (Tfloat)atXYZ(ax,py,z,c,out_value),
+        Ipc = Icpc + 0.5f*(dx*(-Ippc+Inpc) + dx*dx*(2*Ippc-5*Icpc+4*Inpc-Iapc) + dx*dx*dx*(-Ippc+3*Icpc-3*Inpc+Iapc)),
+        Ipcc = (Tfloat)atXYZ(px,y,z,c,out_value),  Iccc = (Tfloat)atXYZ(x, y,z,c,out_value),
+        Incc = (Tfloat)atXYZ(nx,y,z,c,out_value),  Iacc = (Tfloat)atXYZ(ax,y,z,c,out_value),
+        Icc = Iccc + 0.5f*(dx*(-Ipcc+Incc) + dx*dx*(2*Ipcc-5*Iccc+4*Incc-Iacc) + dx*dx*dx*(-Ipcc+3*Iccc-3*Incc+Iacc)),
+        Ipnc = (Tfloat)atXYZ(px,ny,z,c,out_value), Icnc = (Tfloat)atXYZ(x,ny,z,c,out_value),
+        Innc = (Tfloat)atXYZ(nx,ny,z,c,out_value), Ianc = (Tfloat)atXYZ(ax,ny,z,c,out_value),
+        Inc = Icnc + 0.5f*(dx*(-Ipnc+Innc) + dx*dx*(2*Ipnc-5*Icnc+4*Innc-Ianc) + dx*dx*dx*(-Ipnc+3*Icnc-3*Innc+Ianc)),
+        Ipac = (Tfloat)atXYZ(px,ay,z,c,out_value), Icac = (Tfloat)atXYZ(x,ay,z,c,out_value),
+        Inac = (Tfloat)atXYZ(nx,ay,z,c,out_value), Iaac = (Tfloat)atXYZ(ax,ay,z,c,out_value),
+        Iac = Icac + 0.5f*(dx*(-Ipac+Inac) + dx*dx*(2*Ipac-5*Icac+4*Inac-Iaac) + dx*dx*dx*(-Ipac+3*Icac-3*Inac+Iaac)),
+        Ic = Icc + 0.5f*(dy*(-Ipc+Inc) + dy*dy*(2*Ipc-5*Icc+4*Inc-Iac) + dy*dy*dy*(-Ipc+3*Icc-3*Inc+Iac)),
+        Ippn = (Tfloat)atXYZ(px,py,nz,c,out_value), Icpn = (Tfloat)atXYZ(x,py,nz,c,out_value),
+        Inpn = (Tfloat)atXYZ(nx,py,nz,c,out_value), Iapn = (Tfloat)atXYZ(ax,py,nz,c,out_value),
+        Ipn = Icpn + 0.5f*(dx*(-Ippn+Inpn) + dx*dx*(2*Ippn-5*Icpn+4*Inpn-Iapn) + dx*dx*dx*(-Ippn+3*Icpn-3*Inpn+Iapn)),
+        Ipcn = (Tfloat)atXYZ(px,y,nz,c,out_value),  Iccn = (Tfloat)atXYZ(x, y,nz,c,out_value),
+        Incn = (Tfloat)atXYZ(nx,y,nz,c,out_value),  Iacn = (Tfloat)atXYZ(ax,y,nz,c,out_value),
+        Icn = Iccn + 0.5f*(dx*(-Ipcn+Incn) + dx*dx*(2*Ipcn-5*Iccn+4*Incn-Iacn) + dx*dx*dx*(-Ipcn+3*Iccn-3*Incn+Iacn)),
+        Ipnn = (Tfloat)atXYZ(px,ny,nz,c,out_value), Icnn = (Tfloat)atXYZ(x,ny,nz,c,out_value),
+        Innn = (Tfloat)atXYZ(nx,ny,nz,c,out_value), Iann = (Tfloat)atXYZ(ax,ny,nz,c,out_value),
+        Inn = Icnn + 0.5f*(dx*(-Ipnn+Innn) + dx*dx*(2*Ipnn-5*Icnn+4*Innn-Iann) + dx*dx*dx*(-Ipnn+3*Icnn-3*Innn+Iann)),
+        Ipan = (Tfloat)atXYZ(px,ay,nz,c,out_value), Ican = (Tfloat)atXYZ(x,ay,nz,c,out_value),
+        Inan = (Tfloat)atXYZ(nx,ay,nz,c,out_value), Iaan = (Tfloat)atXYZ(ax,ay,nz,c,out_value),
+        Ian = Ican + 0.5f*(dx*(-Ipan+Inan) + dx*dx*(2*Ipan-5*Ican+4*Inan-Iaan) + dx*dx*dx*(-Ipan+3*Ican-3*Inan+Iaan)),
+        In = Icn + 0.5f*(dy*(-Ipn+Inn) + dy*dy*(2*Ipn-5*Icn+4*Inn-Ian) + dy*dy*dy*(-Ipn+3*Icn-3*Inn+Ian)),
+        Ippa = (Tfloat)atXYZ(px,py,az,c,out_value), Icpa = (Tfloat)atXYZ(x,py,az,c,out_value),
+        Inpa = (Tfloat)atXYZ(nx,py,az,c,out_value), Iapa = (Tfloat)atXYZ(ax,py,az,c,out_value),
+        Ipa = Icpa + 0.5f*(dx*(-Ippa+Inpa) + dx*dx*(2*Ippa-5*Icpa+4*Inpa-Iapa) + dx*dx*dx*(-Ippa+3*Icpa-3*Inpa+Iapa)),
+        Ipca = (Tfloat)atXYZ(px,y,az,c,out_value),  Icca = (Tfloat)atXYZ(x, y,az,c,out_value),
+        Inca = (Tfloat)atXYZ(nx,y,az,c,out_value),  Iaca = (Tfloat)atXYZ(ax,y,az,c,out_value),
+        Ica = Icca + 0.5f*(dx*(-Ipca+Inca) + dx*dx*(2*Ipca-5*Icca+4*Inca-Iaca) + dx*dx*dx*(-Ipca+3*Icca-3*Inca+Iaca)),
+        Ipna = (Tfloat)atXYZ(px,ny,az,c,out_value), Icna = (Tfloat)atXYZ(x,ny,az,c,out_value),
+        Inna = (Tfloat)atXYZ(nx,ny,az,c,out_value), Iana = (Tfloat)atXYZ(ax,ny,az,c,out_value),
+        Ina = Icna + 0.5f*(dx*(-Ipna+Inna) + dx*dx*(2*Ipna-5*Icna+4*Inna-Iana) + dx*dx*dx*(-Ipna+3*Icna-3*Inna+Iana)),
+        Ipaa = (Tfloat)atXYZ(px,ay,az,c,out_value), Icaa = (Tfloat)atXYZ(x,ay,az,c,out_value),
+        Inaa = (Tfloat)atXYZ(nx,ay,az,c,out_value), Iaaa = (Tfloat)atXYZ(ax,ay,az,c,out_value),
+        Iaa = Icaa + 0.5f*(dx*(-Ipaa+Inaa) + dx*dx*(2*Ipaa-5*Icaa+4*Inaa-Iaaa) + dx*dx*dx*(-Ipaa+3*Icaa-3*Inaa+Iaaa)),
+        Ia = Ica + 0.5f*(dy*(-Ipa+Ina) + dy*dy*(2*Ipa-5*Ica+4*Ina-Iaa) + dy*dy*dy*(-Ipa+3*Ica-3*Ina+Iaa));
+      return Ic + 0.5f*(dz*(-Ip+In) + dz*dz*(2*Ip-5*Ic+4*In-Ia) + dz*dz*dz*(-Ip+3*Ic-3*In+Ia));
+    }
+
+    //! Return damped pixel value, using cubic interpolation and Dirichlet boundary conditions for the X,Y and Z-coordinates.
+    /**
+       Similar to cubic_atXYZ(float,float,float,int,const T) const, except that you can specify the authorized minimum and maximum of the returned value.
+    **/
+    Tfloat cubic_atXYZ(const float fx, const float fy, const float fz, const int c, const T out_value,
+                       const Tfloat min_value, const Tfloat max_value) const {
+      const Tfloat val = cubic_atXYZ(fx,fy,fz,c,out_value);
+      return val<min_value?min_value:val>max_value?max_value:val;
+    }
+
+    //! Return pixel value, using cubic interpolation and Neumann boundary conditions for the X,Y and Z-coordinates.
+    /**
+       Similar to cubic_atX(float,int,int,int) const, except that the cubic interpolation and boundary checking
+       are achieved both for X,Y and Z-coordinates.
+       \note
+       - If you know your image instance is \e not empty, you may rather use the slightly faster method \c _cubic_atXYZ(float,float,float,int).
+    **/
+    Tfloat cubic_atXYZ(const float fx, const float fy, const float fz, const int c=0) const {
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "cubic_atXYZ() : Empty instance.",
+                                    cimg_instance);
+      return _cubic_atXYZ(fx,fy,fz,c);
+    }
+
+    Tfloat _cubic_atXYZ(const float fx, const float fy, const float fz, const int c=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 int x = (int)nfx, y = (int)nfy, z = (int)nfz;
+      const float dx = nfx - x, dy = nfy - y, dz = nfz - z;
+      const int
+        px = x-1<0?0:x-1, nx = dx>0?x+1:x, ax = x+2>=width()?width()-1:x+2,
+        py = y-1<0?0:y-1, ny = dy>0?y+1:y, ay = y+2>=height()?height()-1:y+2,
+        pz = z-1<0?0:z-1, nz = dz>0?z+1:z, az = z+2>=depth()?depth()-1:z+2;
+      const Tfloat
+        Ippp = (Tfloat)(*this)(px,py,pz,c), Icpp = (Tfloat)(*this)(x,py,pz,c),
+        Inpp = (Tfloat)(*this)(nx,py,pz,c), Iapp = (Tfloat)(*this)(ax,py,pz,c),
+        Ipp = Icpp + 0.5f*(dx*(-Ippp+Inpp) + dx*dx*(2*Ippp-5*Icpp+4*Inpp-Iapp) + dx*dx*dx*(-Ippp+3*Icpp-3*Inpp+Iapp)),
+        Ipcp = (Tfloat)(*this)(px,y,pz,c),  Iccp = (Tfloat)(*this)(x, y,pz,c),
+        Incp = (Tfloat)(*this)(nx,y,pz,c),  Iacp = (Tfloat)(*this)(ax,y,pz,c),
+        Icp = Iccp + 0.5f*(dx*(-Ipcp+Incp) + dx*dx*(2*Ipcp-5*Iccp+4*Incp-Iacp) + dx*dx*dx*(-Ipcp+3*Iccp-3*Incp+Iacp)),
+        Ipnp = (Tfloat)(*this)(px,ny,pz,c), Icnp = (Tfloat)(*this)(x,ny,pz,c),
+        Innp = (Tfloat)(*this)(nx,ny,pz,c), Ianp = (Tfloat)(*this)(ax,ny,pz,c),
+        Inp = Icnp + 0.5f*(dx*(-Ipnp+Innp) + dx*dx*(2*Ipnp-5*Icnp+4*Innp-Ianp) + dx*dx*dx*(-Ipnp+3*Icnp-3*Innp+Ianp)),
+        Ipap = (Tfloat)(*this)(px,ay,pz,c), Icap = (Tfloat)(*this)(x,ay,pz,c),
+        Inap = (Tfloat)(*this)(nx,ay,pz,c), Iaap = (Tfloat)(*this)(ax,ay,pz,c),
+        Iap = Icap + 0.5f*(dx*(-Ipap+Inap) + dx*dx*(2*Ipap-5*Icap+4*Inap-Iaap) + dx*dx*dx*(-Ipap+3*Icap-3*Inap+Iaap)),
+        Ip = Icp + 0.5f*(dy*(-Ipp+Inp) + dy*dy*(2*Ipp-5*Icp+4*Inp-Iap) + dy*dy*dy*(-Ipp+3*Icp-3*Inp+Iap)),
+        Ippc = (Tfloat)(*this)(px,py,z,c), Icpc = (Tfloat)(*this)(x,py,z,c),
+        Inpc = (Tfloat)(*this)(nx,py,z,c), Iapc = (Tfloat)(*this)(ax,py,z,c),
+        Ipc = Icpc + 0.5f*(dx*(-Ippc+Inpc) + dx*dx*(2*Ippc-5*Icpc+4*Inpc-Iapc) + dx*dx*dx*(-Ippc+3*Icpc-3*Inpc+Iapc)),
+        Ipcc = (Tfloat)(*this)(px,y,z,c),  Iccc = (Tfloat)(*this)(x, y,z,c),
+        Incc = (Tfloat)(*this)(nx,y,z,c),  Iacc = (Tfloat)(*this)(ax,y,z,c),
+        Icc = Iccc + 0.5f*(dx*(-Ipcc+Incc) + dx*dx*(2*Ipcc-5*Iccc+4*Incc-Iacc) + dx*dx*dx*(-Ipcc+3*Iccc-3*Incc+Iacc)),
+        Ipnc = (Tfloat)(*this)(px,ny,z,c), Icnc = (Tfloat)(*this)(x,ny,z,c),
+        Innc = (Tfloat)(*this)(nx,ny,z,c), Ianc = (Tfloat)(*this)(ax,ny,z,c),
+        Inc = Icnc + 0.5f*(dx*(-Ipnc+Innc) + dx*dx*(2*Ipnc-5*Icnc+4*Innc-Ianc) + dx*dx*dx*(-Ipnc+3*Icnc-3*Innc+Ianc)),
+        Ipac = (Tfloat)(*this)(px,ay,z,c), Icac = (Tfloat)(*this)(x,ay,z,c),
+        Inac = (Tfloat)(*this)(nx,ay,z,c), Iaac = (Tfloat)(*this)(ax,ay,z,c),
+        Iac = Icac + 0.5f*(dx*(-Ipac+Inac) + dx*dx*(2*Ipac-5*Icac+4*Inac-Iaac) + dx*dx*dx*(-Ipac+3*Icac-3*Inac+Iaac)),
+        Ic = Icc + 0.5f*(dy*(-Ipc+Inc) + dy*dy*(2*Ipc-5*Icc+4*Inc-Iac) + dy*dy*dy*(-Ipc+3*Icc-3*Inc+Iac)),
+        Ippn = (Tfloat)(*this)(px,py,nz,c), Icpn = (Tfloat)(*this)(x,py,nz,c),
+        Inpn = (Tfloat)(*this)(nx,py,nz,c), Iapn = (Tfloat)(*this)(ax,py,nz,c),
+        Ipn = Icpn + 0.5f*(dx*(-Ippn+Inpn) + dx*dx*(2*Ippn-5*Icpn+4*Inpn-Iapn) + dx*dx*dx*(-Ippn+3*Icpn-3*Inpn+Iapn)),
+        Ipcn = (Tfloat)(*this)(px,y,nz,c),  Iccn = (Tfloat)(*this)(x, y,nz,c),
+        Incn = (Tfloat)(*this)(nx,y,nz,c),  Iacn = (Tfloat)(*this)(ax,y,nz,c),
+        Icn = Iccn + 0.5f*(dx*(-Ipcn+Incn) + dx*dx*(2*Ipcn-5*Iccn+4*Incn-Iacn) + dx*dx*dx*(-Ipcn+3*Iccn-3*Incn+Iacn)),
+        Ipnn = (Tfloat)(*this)(px,ny,nz,c), Icnn = (Tfloat)(*this)(x,ny,nz,c),
+        Innn = (Tfloat)(*this)(nx,ny,nz,c), Iann = (Tfloat)(*this)(ax,ny,nz,c),
+        Inn = Icnn + 0.5f*(dx*(-Ipnn+Innn) + dx*dx*(2*Ipnn-5*Icnn+4*Innn-Iann) + dx*dx*dx*(-Ipnn+3*Icnn-3*Innn+Iann)),
+        Ipan = (Tfloat)(*this)(px,ay,nz,c), Ican = (Tfloat)(*this)(x,ay,nz,c),
+        Inan = (Tfloat)(*this)(nx,ay,nz,c), Iaan = (Tfloat)(*this)(ax,ay,nz,c),
+        Ian = Ican + 0.5f*(dx*(-Ipan+Inan) + dx*dx*(2*Ipan-5*Ican+4*Inan-Iaan) + dx*dx*dx*(-Ipan+3*Ican-3*Inan+Iaan)),
+        In = Icn + 0.5f*(dy*(-Ipn+Inn) + dy*dy*(2*Ipn-5*Icn+4*Inn-Ian) + dy*dy*dy*(-Ipn+3*Icn-3*Inn+Ian)),
+        Ippa = (Tfloat)(*this)(px,py,az,c), Icpa = (Tfloat)(*this)(x,py,az,c),
+        Inpa = (Tfloat)(*this)(nx,py,az,c), Iapa = (Tfloat)(*this)(ax,py,az,c),
+        Ipa = Icpa + 0.5f*(dx*(-Ippa+Inpa) + dx*dx*(2*Ippa-5*Icpa+4*Inpa-Iapa) + dx*dx*dx*(-Ippa+3*Icpa-3*Inpa+Iapa)),
+        Ipca = (Tfloat)(*this)(px,y,az,c),  Icca = (Tfloat)(*this)(x, y,az,c),
+        Inca = (Tfloat)(*this)(nx,y,az,c),  Iaca = (Tfloat)(*this)(ax,y,az,c),
+        Ica = Icca + 0.5f*(dx*(-Ipca+Inca) + dx*dx*(2*Ipca-5*Icca+4*Inca-Iaca) + dx*dx*dx*(-Ipca+3*Icca-3*Inca+Iaca)),
+        Ipna = (Tfloat)(*this)(px,ny,az,c), Icna = (Tfloat)(*this)(x,ny,az,c),
+        Inna = (Tfloat)(*this)(nx,ny,az,c), Iana = (Tfloat)(*this)(ax,ny,az,c),
+        Ina = Icna + 0.5f*(dx*(-Ipna+Inna) + dx*dx*(2*Ipna-5*Icna+4*Inna-Iana) + dx*dx*dx*(-Ipna+3*Icna-3*Inna+Iana)),
+        Ipaa = (Tfloat)(*this)(px,ay,az,c), Icaa = (Tfloat)(*this)(x,ay,az,c),
+        Inaa = (Tfloat)(*this)(nx,ay,az,c), Iaaa = (Tfloat)(*this)(ax,ay,az,c),
+        Iaa = Icaa + 0.5f*(dx*(-Ipaa+Inaa) + dx*dx*(2*Ipaa-5*Icaa+4*Inaa-Iaaa) + dx*dx*dx*(-Ipaa+3*Icaa-3*Inaa+Iaaa)),
+        Ia = Ica + 0.5f*(dy*(-Ipa+Ina) + dy*dy*(2*Ipa-5*Ica+4*Ina-Iaa) + dy*dy*dy*(-Ipa+3*Ica-3*Ina+Iaa));
+      return Ic + 0.5f*(dz*(-Ip+In) + dz*dz*(2*Ip-5*Ic+4*In-Ia) + dz*dz*dz*(-Ip+3*Ic-3*In+Ia));
+    }
+
+    //! Return damped pixel value, using cubic interpolation and Neumann boundary conditions for the X,Y and Z-coordinates.
+    /**
+       Similar to cubic_atXYZ(float,float,float,int) const, except that you can specify the authorized minimum and maximum of the returned value.
+    **/
+    Tfloat cubic_atXYZ(const float fx, const float fy, const float fz, const int c,
+                       const Tfloat min_value, const Tfloat max_value) const {
+      const Tfloat val = cubic_atXYZ(fx,fy,fz,c);
+      return val<min_value?min_value:val>max_value?max_value:val;
+    }
+
+    Tfloat _cubic_atXYZ(const float fx, const float fy, const float fz, const int c,
+                        const Tfloat min_value, const Tfloat max_value) const {
+      const Tfloat val = _cubic_atXYZ(fx,fy,fz,c);
+      return val<min_value?min_value:val>max_value?max_value:val;
+    }
+
+    //! Set pixel value, using linear interpolation for the X and Y-coordinates.
+    /**
+       Set pixel value at specified coordinates (\c fx,\c fy,\c z,\c c) in the image instance, in a way that the value is spread
+       amongst several neighbors if the pixel coordinates are indeed float-valued.
+       \param value : Pixel value to set.
+       \param fx : X-coordinate of the pixel value (float-valued).
+       \param fy : Y-coordinate of the pixel value (float-valued).
+       \param z : Z-coordinate of the pixel value.
+       \param c : C-coordinate of the pixel value.
+       \param is_added : Boolean telling if the pixel value is added to (\c true), or simply replace (\c false) the current image pixel(s).
+       \return A reference to the current image instance.
+       \note
+       - If specified coordinates are outside image bounds, no operations are performed.
+       \see linear_atXY(),
+           set_linear_atXYZ().
+    **/
+    CImg<T>& set_linear_atXY(const T& value, const float fx, const float fy=0, const int z=0, const int c=0,
+                             const bool is_added=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<depth() && c>=0 && c<spectrum()) {
+        if (y>=0 && y<height()) {
+          if (x>=0 && x<width()) {
+            const float w1 = (1-dx)*(1-dy), w2 = is_added?1:(1-w1);
+            (*this)(x,y,z,c) = (T)(w1*value + w2*(*this)(x,y,z,c));
+          }
+          if (nx>=0 && nx<width()) {
+            const float w1 = dx*(1-dy), w2 = is_added?1:(1-w1);
+            (*this)(nx,y,z,c) = (T)(w1*value + w2*(*this)(nx,y,z,c));
+          }
+        }
+        if (ny>=0 && ny<height()) {
+          if (x>=0 && x<width()) {
+            const float w1 = (1-dx)*dy, w2 = is_added?1:(1-w1);
+            (*this)(x,ny,z,c) = (T)(w1*value + w2*(*this)(x,ny,z,c));
+          }
+          if (nx>=0 && nx<width()) {
+            const float w1 = dx*dy, w2 = is_added?1:(1-w1);
+            (*this)(nx,ny,z,c) = (T)(w1*value + w2*(*this)(nx,ny,z,c));
+          }
+        }
+      }
+      return *this;
+    }
+
+    //! Set pixel value, using linear interpolation for the X,Y and Z-coordinates.
+    /**
+       Similar to set_linear_atXY(const T&,float,float,int,int,bool), except that the linear interpolation
+       is achieved both for X,Y and Z-coordinates.
+    **/
+    CImg<T>& set_linear_atXYZ(const T& value, const float fx, const float fy=0, const float fz=0, const int c=0,
+                              const bool is_added=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 (c>=0 && c<spectrum()) {
+        if (z>=0 && z<depth()) {
+          if (y>=0 && y<height()) {
+            if (x>=0 && x<width()) {
+              const float w1 = (1-dx)*(1-dy)*(1-dz), w2 = is_added?1:(1-w1);
+              (*this)(x,y,z,c) = (T)(w1*value + w2*(*this)(x,y,z,c));
+            }
+            if (nx>=0 && nx<width()) {
+              const float w1 = dx*(1-dy)*(1-dz), w2 = is_added?1:(1-w1);
+              (*this)(nx,y,z,c) = (T)(w1*value + w2*(*this)(nx,y,z,c));
+            }
+          }
+          if (ny>=0 && ny<height()) {
+            if (x>=0 && x<width()) {
+              const float w1 = (1-dx)*dy*(1-dz), w2 = is_added?1:(1-w1);
+              (*this)(x,ny,z,c) = (T)(w1*value + w2*(*this)(x,ny,z,c));
+            }
+            if (nx>=0 && nx<width()) {
+              const float w1 = dx*dy*(1-dz), w2 = is_added?1:(1-w1);
+              (*this)(nx,ny,z,c) = (T)(w1*value + w2*(*this)(nx,ny,z,c));
+            }
+          }
+        }
+        if (nz>=0 && nz<depth()) {
+          if (y>=0 && y<height()) {
+            if (x>=0 && x<width()) {
+              const float w1 = (1-dx)*(1-dy)*dz, w2 = is_added?1:(1-w1);
+              (*this)(x,y,nz,c) = (T)(w1*value + w2*(*this)(x,y,nz,c));
+            }
+            if (nx>=0 && nx<width()) {
+              const float w1 = dx*(1-dy)*dz, w2 = is_added?1:(1-w1);
+              (*this)(nx,y,nz,c) = (T)(w1*value + w2*(*this)(nx,y,nz,c));
+            }
+          }
+          if (ny>=0 && ny<height()) {
+            if (x>=0 && x<width()) {
+              const float w1 = (1-dx)*dy*dz, w2 = is_added?1:(1-w1);
+              (*this)(x,ny,nz,c) = (T)(w1*value + w2*(*this)(x,ny,nz,c));
+            }
+            if (nx>=0 && nx<width()) {
+              const float w1 = dx*dy*dz, w2 = is_added?1:(1-w1);
+              (*this)(nx,ny,nz,c) = (T)(w1*value + w2*(*this)(nx,ny,nz,c));
+            }
+          }
+        }
+      }
+      return *this;
+    }
+
+    //! Return a C-string containing a list of all values of the image instance.
+    /**
+       Return a new \c CImg<char> image whose buffer data() is a \c char* string describing the list of all pixel values
+       of the image instance (written in base 10), separated by specified \c separator character.
+       \param separator : a \c char character which specifies the separator between values in the returned C-string.
+       \param max_size : Maximum size of the returned image.
+       \note
+       - The returned image is never empty.
+       - For an empty image instance, the returned string is <tt>""</tt>.
+       - If \c max_size is equal to \c 0, there are no limits on the size of the returned string.
+       - Otherwise, if the maximum number of string characters is exceeded, the value string is cut off
+         and terminated by character \c '\0'. In that case, the returned image size is <tt>max_size + 1</tt>.
+       \see pixel_type().
+    **/
+    CImg<charT> value_string(const char separator=',', const unsigned int max_size=0) const {
+      if (is_empty()) return CImg<charT>::string("");
+      CImgList<charT> items;
+      char s_item[256] = { 0 };
+      const T *ptrs = _data;
+      unsigned int string_size = 0;
+      for (unsigned int off = 0, siz = (unsigned int)size(); off<siz && string_size<=max_size; ++off) {
+        const unsigned int printed_size = 1U + cimg_snprintf(s_item,sizeof(s_item),cimg::type<T>::format(),cimg::type<T>::format(*(ptrs++)));
+        CImg<charT> item(s_item,printed_size);
+        item[printed_size-1] = separator;
+        item.move_to(items);
+        if (max_size) string_size+=printed_size;
+      }
+      CImg<charT> res;
+      (items>'x').move_to(res);
+      if (max_size && res._width>max_size) res.crop(0,max_size);
+      res.back() = 0;
+      return res;
+    }
+
+    //@}
+    //-------------------------------------
+    //
+    //! \name Instance Checking
+    //@{
+    //-------------------------------------
+
+    //! Test shared state of the pixel buffer.
+    /**
+       Return \c true if image instance has a shared memory buffer, and \c false otherwise.
+       \note
+       - A shared image do not own his pixel buffer data() and will not deallocate it on destruction.
+       - Most of the time, a \c CImg<T> image instance will \e not be shared.
+       - A shared image can only be obtained by a limited set of constructors and methods (see list below).
+       \see CImg(const t *const,unsigned int,unsigned int,unsigned int,unsigned int,bool),
+           CImg(const CImg<t>&),
+           CImg(const CImg<t>&,bool),
+           assign(const t *const,unsigned int,unsigned int,unsigned int,unsigned int,bool),
+           get_shared_points(),
+           get_shared_line(),
+           get_shared_lines(),
+           get_shared_plane(),
+           get_shared_planes(),
+           get_shared_channel(),
+           get_shared_channels(),
+           get_shared().
+    **/
+    bool is_shared() const {
+      return _is_shared;
+    }
+
+    //! Test if image instance is empty.
+    /**
+       Return \c true, if image instance is empty, i.e. does \e not contain any pixel values, has dimensions \c 0 x \c 0 x \c 0 x \c 0
+       and a pixel buffer pointer set to \c 0 (null pointer), and \c false otherwise.
+       \see CImg(),
+           assign().
+    **/
+    bool is_empty() const {
+      return !(_data && _width && _height && _depth && _spectrum);
+    }
+
+    //! Test if image instance contains a 'inf' value.
+    /**
+       Return \c true, if image instance contains a 'inf' value, and \c false otherwise.
+       \see is_nan().
+    **/
+    bool is_inf() const {
+      if (cimg::type<T>::is_float()) cimg_for(*this,p,T) if (cimg::type<T>::is_inf((float)*p)) return true;
+      return false;
+    }
+
+    //! Test if image instance contains a 'nan' value.
+    /**
+       Return \c true, if image instance contains a 'nan' value, and \c false otherwise.
+       \see is_inf().
+    **/
+    bool is_nan() const {
+      if (cimg::type<T>::is_float()) cimg_for(*this,p,T) if (cimg::type<T>::is_nan((float)*p)) return true;
+      return false;
+    }
+
+    //! Test if image width is equal to specified value.
+    bool is_sameX(const unsigned int size_x) const {
+      return _width==size_x;
+    }
+
+    //! Test if image width is equal to specified value.
+    template<typename t>
+    bool is_sameX(const CImg<t>& img) const {
+      return is_sameX(img._width);
+    }
+
+    //! Test if image width is equal to specified value.
+    bool is_sameX(const CImgDisplay& disp) const {
+      return is_sameX(disp._width);
+    }
+
+    //! Test if image height is equal to specified value.
+    bool is_sameY(const unsigned int size_y) const {
+      return _height==size_y;
+    }
+
+    //! Test if image height is equal to specified value.
+    template<typename t>
+    bool is_sameY(const CImg<t>& img) const {
+      return is_sameY(img._height);
+    }
+
+    //! Test if image height is equal to specified value.
+    bool is_sameY(const CImgDisplay& disp) const {
+      return is_sameY(disp._height);
+    }
+
+    //! Test if image depth is equal to specified value.
+    bool is_sameZ(const unsigned int size_z) const {
+      return _depth==size_z;
+    }
+
+    //! Test if image depth is equal to specified value.
+    template<typename t>
+    bool is_sameZ(const CImg<t>& img) const {
+      return is_sameZ(img._depth);
+    }
+
+    //! Test if image spectrum is equal to specified value.
+    bool is_sameC(const unsigned int size_c) const {
+      return _spectrum==size_c;
+    }
+
+    //! Test if image spectrum is equal to specified value.
+    template<typename t>
+    bool is_sameC(const CImg<t>& img) const {
+      return is_sameC(img._spectrum);
+    }
+
+    //! Test if image width and height are equal to specified values.
+    /**
+       Test if is_sameX(unsigned int) const and is_sameY(unsigned int) const are both verified.
+    **/
+    bool is_sameXY(const unsigned int size_x, const unsigned int size_y) const {
+      return _width==size_x && _height==size_y;
+    }
+
+    //! Test if image width and height are the same as that of another image.
+    /**
+       Test if is_sameX(const CImg<t>&) const and is_sameY(const CImg<t>&) const are both verified.
+    **/
+    template<typename t>
+    bool is_sameXY(const CImg<t>& img) const {
+      return is_sameXY(img._width,img._height);
+    }
+
+    //! Test if image width and height are the same as that of an existing display window.
+    /**
+       Test if is_sameX(const CImgDisplay&) const and is_sameY(const CImgDisplay&) const are both verified.
+    **/
+    bool is_sameXY(const CImgDisplay& disp) const {
+      return is_sameXY(disp._width,disp._height);
+    }
+
+    //! Test if image width and depth are equal to specified values.
+    /**
+       Test if is_sameX(unsigned int) const and is_sameZ(unsigned int) const are both verified.
+    **/
+    bool is_sameXZ(const unsigned int size_x, const unsigned int size_z) const {
+      return _width==size_x && _depth==size_z;
+    }
+
+    //! Test if image width and depth are the same as that of another image.
+    /**
+       Test if is_sameX(const CImg<t>&) const and is_sameZ(const CImg<t>&) const are both verified.
+    **/
+    template<typename t>
+    bool is_sameXZ(const CImg<t>& img) const {
+      return is_sameXZ(img._width,img._depth);
+    }
+
+    //! Test if image width and spectrum are equal to specified values.
+    /**
+       Test if is_sameX(unsigned int) const and is_sameC(unsigned int) const are both verified.
+    **/
+    bool is_sameXC(const unsigned int size_x, const unsigned int size_c) const {
+      return _width==size_x && _spectrum==size_c;
+    }
+
+    //! Test if image width and spectrum are the same as that of another image.
+    /**
+       Test if is_sameX(const CImg<t>&) const and is_sameC(const CImg<t>&) const are both verified.
+    **/
+    template<typename t>
+    bool is_sameXC(const CImg<t>& img) const {
+      return is_sameXC(img._width,img._spectrum);
+    }
+
+    //! Test if image height and depth are equal to specified values.
+    /**
+       Test if is_sameY(unsigned int) const and is_sameZ(unsigned int) const are both verified.
+    **/
+    bool is_sameYZ(const unsigned int size_y, const unsigned int size_z) const {
+      return _height==size_y && _depth==size_z;
+    }
+
+    //! Test if image height and depth are the same as that of another image.
+    /**
+       Test if is_sameY(const CImg<t>&) const and is_sameZ(const CImg<t>&) const are both verified.
+    **/
+    template<typename t>
+    bool is_sameYZ(const CImg<t>& img) const {
+      return is_sameYZ(img._height,img._depth);
+    }
+
+    //! Test if image height and spectrum are equal to specified values.
+    /**
+       Test if is_sameY(unsigned int) const and is_sameC(unsigned int) const are both verified.
+    **/
+    bool is_sameYC(const unsigned int size_y, const unsigned int size_c) const {
+      return _height==size_y && _spectrum==size_c;
+    }
+
+    //! Test if image height and spectrum are the same as that of another image.
+    /**
+       Test if is_sameY(const CImg<t>&) const and is_sameC(const CImg<t>&) const are both verified.
+    **/
+    template<typename t>
+    bool is_sameYC(const CImg<t>& img) const {
+      return is_sameYC(img._height,img._spectrum);
+    }
+
+    //! Test if image depth and spectrum are equal to specified values.
+    /**
+       Test if is_sameZ(unsigned int) const and is_sameC(unsigned int) const are both verified.
+    **/
+    bool is_sameZC(const unsigned int size_z, const unsigned int size_c) const {
+      return _depth==size_z && _spectrum==size_c;
+    }
+
+    //! Test if image depth and spectrum are the same as that of another image.
+    /**
+       Test if is_sameZ(const CImg<t>&) const and is_sameC(const CImg<t>&) const are both verified.
+    **/
+    template<typename t>
+    bool is_sameZC(const CImg<t>& img) const {
+      return is_sameZC(img._depth,img._spectrum);
+    }
+
+    //! Test if image width, height and depth are equal to specified values.
+    /**
+       Test if is_sameXY(unsigned int,unsigned int) const and is_sameZ(unsigned int) const are both verified.
+    **/
+    bool is_sameXYZ(const unsigned int size_x, const unsigned int size_y, const unsigned int size_z) const {
+      return is_sameXY(size_x,size_y) && _depth==size_z;
+    }
+
+    //! Test if image width, height and depth are the same as that of another image.
+    /**
+       Test if is_sameXY(const CImg<t>&) const and is_sameZ(const CImg<t>&) const are both verified.
+    **/
+    template<typename t>
+    bool is_sameXYZ(const CImg<t>& img) const {
+      return is_sameXYZ(img._width,img._height,img._depth);
+    }
+
+    //! Test if image width, height and spectrum are equal to specified values.
+    /**
+       Test if is_sameXY(unsigned int,unsigned int) const and is_sameC(unsigned int) const are both verified.
+    **/
+    bool is_sameXYC(const unsigned int size_x, const unsigned int size_y, const unsigned int size_c) const {
+      return is_sameXY(size_x,size_y) && _spectrum==size_c;
+    }
+
+    //! Test if image width, height and spectrum are the same as that of another image.
+    /**
+       Test if is_sameXY(const CImg<t>&) const and is_sameC(const CImg<t>&) const are both verified.
+    **/
+    template<typename t>
+    bool is_sameXYC(const CImg<t>& img) const {
+      return is_sameXYC(img._width,img._height,img._spectrum);
+    }
+
+    //! Test if image width, depth and spectrum are equal to specified values.
+    /**
+       Test if is_sameXZ(unsigned int,unsigned int) const and is_sameC(unsigned int) const are both verified.
+    **/
+    bool is_sameXZC(const unsigned int size_x, const unsigned int size_z, const unsigned int size_c) const {
+      return is_sameXZ(size_x,size_z) && _spectrum==size_c;
+    }
+
+    //! Test if image width, depth and spectrum are the same as that of another image.
+    /**
+       Test if is_sameXZ(const CImg<t>&) const and is_sameC(const CImg<t>&) const are both verified.
+    **/
+    template<typename t>
+    bool is_sameXZC(const CImg<t>& img) const {
+      return is_sameXZC(img._width,img._depth,img._spectrum);
+    }
+
+    //! Test if image height, depth and spectrum are equal to specified values.
+    /**
+       Test if is_sameYZ(unsigned int,unsigned int) const and is_sameC(unsigned int) const are both verified.
+    **/
+    bool is_sameYZC(const unsigned int size_y, const unsigned int size_z, const unsigned int size_c) const {
+      return is_sameYZ(size_y,size_z) && _spectrum==size_c;
+    }
+
+    //! Test if image height, depth and spectrum are the same as that of another image.
+    /**
+       Test if is_sameYZ(const CImg<t>&) const and is_sameC(const CImg<t>&) const are both verified.
+    **/
+    template<typename t>
+    bool is_sameYZC(const CImg<t>& img) const {
+      return is_sameYZC(img._height,img._depth,img._spectrum);
+    }
+
+    //! Test if image width, height, depth and spectrum are equal to specified values.
+    /**
+       Test if is_sameXYZ(unsigned int,unsigned int,unsigned int) const and is_sameC(unsigned int) const are both verified.
+    **/
+    bool is_sameXYZC(const unsigned int size_x, const unsigned int size_y, const unsigned int size_z, const unsigned int size_c) const {
+      return is_sameXYZ(size_x,size_y,size_z) && _spectrum==size_c;
+    }
+
+    //! Test if image width, height, depth and spectrum are the same as that of another image.
+    /**
+       Test if is_sameXYZ(const CImg<t>&) const and is_sameC(const CImg<t>&) const are both verified.
+    **/
+    template<typename t>
+    bool is_sameXYZC(const CImg<t>& img) const {
+      return is_sameXYZC(img._width,img._height,img._depth,img._spectrum);
+    }
+
+    //! Test if specified coordinates are inside image bounds.
+    /**
+       Return \c true if pixel located at (\c x,\c y,\c z,\c c) is inside bounds of the image instance, and \c false otherwise.
+       \param x : X-coordinate of the pixel value.
+       \param y : Y-coordinate of the pixel value.
+       \param z : Z-coordinate of the pixel value.
+       \param c : C-coordinate of the pixel value.
+       \note
+       - Return \c true only if all these conditions are verified :
+         - The image instance is \e not empty.
+         - <tt>0<=x<=\ref width()-1</tt>.
+         - <tt>0<=y<=\ref height()-1</tt>.
+         - <tt>0<=z<=\ref depth()-1</tt>.
+         - <tt>0<=c<=\ref spectrum()-1</tt>.
+       \see contains(const T&,t&,t&,t&,t&) const,
+           contains(const T&,t&,t&,t&) const,
+           contains(const T&,t&,t&) const,
+           contains(const T&,t&) const,
+           contains(const T&) const.
+    **/
+    bool containsXYZC(const int x, const int y=0, const int z=0, const int c=0) const {
+      return !is_empty() && x>=0 && x<width() && y>=0 && y<height() && z>=0 && z<depth() && c>=0 && c<spectrum();
+    }
+
+    //! Test if pixel value is inside image bounds and get its X,Y,Z and C-coordinates.
+    /**
+       Return \c true, if specified reference refers to a pixel value inside bounds of the image instance, and \c false otherwise.
+       \param pixel : Reference to pixel value to test.
+       \param[out] x : X-coordinate of the pixel value, if test succeeds.
+       \param[out] y : Y-coordinate of the pixel value, if test succeeds.
+       \param[out] z : Z-coordinate of the pixel value, if test succeeds.
+       \param[out] c : C-coordinate of the pixel value, if test succeeds.
+       \note
+       - Useful to convert an offset to a  buffer value into pixel value coordinates :
+       \code
+       const CImg<float> img(100,100,1,3);      // Construct a 100x100 RGB color image.
+       const unsigned int offset = 1249;        // Offset to the pixel (49,12,0,0).
+       unsigned int x,y,z,c;
+       if (img.contains(img[offset],x,y,z,c)) { // Convert offset to (x,y,z,c) coordinates.
+         std::printf("Offset %u refers to pixel located at (%u,%u,%u,%u).\n",
+                     offset,x,y,z,c);
+       }
+       \endcode
+       \see containsXYZC(int,int,int,int) const,
+           contains(const T&,t&,t&,t&) const,
+           contains(const T&,t&,t&) const,
+           contains(const T&,t&) const,
+           contains(const T&) const.
+    **/
+    template<typename t>
+    bool contains(const T& pixel, t& x, t& y, t& z, t& c) const {
+      const unsigned int wh = _width*_height, whd = wh*_depth, siz = whd*_spectrum;
+      const T *const ppixel = &pixel;
+      if (is_empty() || ppixel<_data || ppixel>=_data+siz) return false;
+      unsigned int off = (unsigned int)(ppixel - _data);
+      const unsigned int nc = off/whd;
+      off%=whd;
+      const unsigned int nz = off/wh;
+      off%=wh;
+      const unsigned int ny = off/_width, nx = off%_width;
+      x = (t)nx; y = (t)ny; z = (t)nz; c = (t)nc;
+      return true;
+    }
+
+    //! Test if pixel value is inside image bounds and get its X,Y and Z-coordinates.
+    /**
+       Similar to contains(const T&,t&,t&,t&,t&) const, except that only the X,Y and Z-coordinates are set.
+    **/
+    template<typename t>
+    bool contains(const T& pixel, t& x, t& y, t& z) const {
+      const unsigned int wh = _width*_height, whd = wh*_depth, siz = whd*_spectrum;
+      const T *const ppixel = &pixel;
+      if (is_empty() || ppixel<_data || ppixel>=_data+siz) return false;
+      unsigned int off = ((unsigned int)(ppixel - _data))%whd;
+      const unsigned int nz = off/wh;
+      off%=wh;
+      const unsigned int ny = off/_width, nx = off%_width;
+      x = (t)nx; y = (t)ny; z = (t)nz;
+      return true;
+    }
+
+    //! Test if pixel value is inside image bounds and get its X and Y-coordinates.
+    /**
+       Similar to contains(const T&,t&,t&,t&,t&) const, except that only the X and Y-coordinates are set.
+    **/
+    template<typename t>
+    bool contains(const T& pixel, t& x, t& y) const {
+      const unsigned int wh = _width*_height, siz = wh*_depth*_spectrum;
+      const T *const ppixel = &pixel;
+      if (is_empty() || ppixel<_data || ppixel>=_data+siz) return false;
+      unsigned int off = ((unsigned int)(ppixel - _data))%wh;
+      const unsigned int ny = off/_width, nx = off%_width;
+      x = (t)nx; y = (t)ny;
+      return true;
+    }
+
+    //! Test if pixel value is inside image bounds and get its X-coordinate.
+    /**
+       Similar to contains(const T&,t&,t&,t&,t&) const, except that only the X-coordinate is set.
+    **/
+    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 int)(ppixel - _data))%_width);
+      return true;
+    }
+
+    //! Test if pixel value is inside image bounds.
+    /**
+       Similar to contains(const T&,t&,t&,t&,t&) const, except that no pixel coordinates are set.
+    **/
+    bool contains(const T& pixel) const {
+      const T *const ppixel = &pixel;
+      return !is_empty() && ppixel>=_data && ppixel<_data + size();
+    }
+
+    //! Test if pixel buffers of instance and input images overlap.
+    /**
+       Return \c true, if pixel buffers attached to image instance and input image \c img overlap, and \c false otherwise.
+       \param img : Input image to compare with.
+       \note
+       - Buffer overlapping may happen when manipulating \e shared images.
+       - If two image buffers overlap, operating on one of the image will probably modify the other one.
+       - Most of the time, \c CImg<T> instances are \e non-shared and do not overlap between each others.
+       \par Example
+       \code
+       const CImg<float>
+         img1("reference.jpg"),             // Load RGB-color image.
+         img2 = img1.get_shared_channel(1); // Get shared version of the green channel.
+       if (img1.is_overlapped(img2)) {      // Test succeeds, 'img1' and 'img2' overlaps.
+         std::printf("Buffers overlap !\n");
+       }
+       \endcode
+       \see is_shared().
+    **/
+    template<typename t>
+    bool is_overlapped(const CImg<t>& img) const {
+      const unsigned int csiz = size(), isiz = img.size();
+      return !((void*)(_data + csiz)<=(void*)img._data || (void*)_data>=(void*)(img._data + isiz));
+    }
+
+    //! Test if the set {\c *this,\c primitives,\c colors,\c opacities} defines a valid 3d object.
+    /**
+       Return \c true is the 3d object represented by the set {\c *this,\c primitives,\c colors,\c opacities} defines a
+       valid 3d object, and \c false otherwise. The vertex coordinates are defined by the instance image.
+       \param primitives : List of primitives of the 3d object.
+       \param colors : List of colors of the 3d object.
+       \param opacities : List (or image) of opacities of the 3d object.
+       \param is_full_check : Boolean telling if full checking of the 3d object must be performed.
+       \param[out] error_message : C-string to contain the error message, if the test does not succeed.
+       \note
+       - Set \c is_full_checking to \c false to speed-up the 3d object checking. In this case, only the size of
+         each 3d object component is checked.
+       - Size of the string \c error_message should be at least 128-bytes long, to be able to contain the error message.
+       \see is_CImg3d(),
+           draw_object3d(),
+           display_object3d().
+    **/
+    template<typename tp, typename tc, typename to>
+    bool is_object3d(const CImgList<tp>& primitives,
+                     const CImgList<tc>& colors,
+                     const to& opacities,
+                     const bool is_full_check=true,
+                     char *const error_message=0) const {
+      if (error_message) *error_message = 0;
+
+      // Check consistency for the particular case of an empty 3d object.
+      if (is_empty()) {
+        if (primitives || colors || opacities) {
+          if (error_message) std::sprintf(error_message,
+                                          "3d object has no vertices but %u primitives, %u colors and %u opacities",
+                                          primitives._width,colors._width,opacities.size());
+          return false;
+        }
+        return true;
+      }
+
+      // Check consistency of vertices.
+      if (_height!=3 || _depth>1 || _spectrum>1) { // Check vertices dimensions.
+        if (error_message) std::sprintf(error_message,
+                                        "3d object (%u,%u) has invalid vertices dimensions (%u,%u,%u,%u)",
+                                        _width,primitives._width,_width,_height,_depth,_spectrum);
+        return false;
+      }
+      if (colors._width>primitives._width+1) {
+        if (error_message) std::sprintf(error_message,
+                                        "3d object (%u,%u) defines %u colors",
+                                        _width,primitives._width,colors._width);
+        return false;
+      }
+      if (opacities.size()>primitives._width) {
+        if (error_message) std::sprintf(error_message,
+                                        "3d object (%u,%u) defines %u opacities",
+                                        _width,primitives._width,opacities.size());
+        return false;
+      }
+      if (!is_full_check) return true;
+
+      // Check consistency of primitives.
+      cimglist_for(primitives,l) {
+        const CImg<tp>& primitive = primitives[l];
+        const unsigned int psiz = primitive.size();
+        switch (psiz) {
+        case 1 : { // Point.
+          const unsigned int i0 = (unsigned int)primitive(0);
+          if (i0>=_width) {
+            if (error_message) std::sprintf(error_message,
+                                            "3d object (%u,%u) refers to invalid vertex indice %u in point primitive %u",
+                                            _width,primitives._width,i0,l);
+            return false;
+          }
+        } break;
+        case 5 : { // Sphere.
+          const unsigned int
+            i0 = (unsigned int)primitive(0),
+            i1 = (unsigned int)primitive(1);
+          if (i0>=_width || i1>=_width) {
+            if (error_message) std::sprintf(error_message,
+                                            "3d object (%u,%u) refers to invalid vertex indices (%u,%u) in sphere primitive %u",
+                                            _width,primitives._width,i0,i1,l);
+            return false;
+          }
+        } break;
+        case 2 : // Segment.
+        case 6 : {
+          const unsigned int
+            i0 = (unsigned int)primitive(0),
+            i1 = (unsigned int)primitive(1);
+          if (i0>=_width || i1>=_width) {
+            if (error_message) std::sprintf(error_message,
+                                            "3d object (%u,%u) refers to invalid vertex indices (%u,%u) in segment primitive %u",
+                                            _width,primitives._width,i0,i1,l);
+            return false;
+          }
+        } break;
+        case 3 : // Triangle.
+        case 9 : {
+          const unsigned int
+            i0 = (unsigned int)primitive(0),
+            i1 = (unsigned int)primitive(1),
+            i2 = (unsigned int)primitive(2);
+          if (i0>=_width || i1>=_width || i2>=_width) {
+            if (error_message) std::sprintf(error_message,
+                                            "3d object (%u,%u) refers to invalid vertex indices (%u,%u,%u) in triangle primitive %u",
+                                            _width,primitives._width,i0,i1,i2,l);
+            return false;
+          }
+        } break;
+        case 4 : // Quadrangle.
+        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);
+          if (i0>=_width || i1>=_width || i2>=_width || i3>=_width) {
+            if (error_message) std::sprintf(error_message,
+                                            "3d object (%u,%u) refers to invalid vertex indices (%u,%u,%u,%u) in quadrangle primitive %u",
+                                            _width,primitives._width,i0,i1,i2,i3,l);
+            return false;
+          }
+        } break;
+        default :
+          if (error_message) std::sprintf(error_message,
+                                          "3d object has invalid primitive %u of size %u",
+                                          l,psiz);
+          return false;
+        }
+      }
+
+      // Check consistency of colors.
+      cimglist_for(colors,c) {
+        const CImg<tc>& color = colors[c];
+        if (!color) {
+          if (error_message) std::sprintf(error_message,
+                                          "3d object has empty color for primitive %u",
+                                          c);
+          return false;
+        }
+      }
+
+      // Check consistency of light texture.
+      if (colors._width>primitives._width) {
+        const CImg<tc> &light = colors.back();
+        if (!light || light._depth>1) {
+          if (error_message) std::sprintf(error_message,
+                                          "3d object has invalid light texture (%u,%u,%u,%u)",
+                                          light._width,light._height,light._depth,light._spectrum);
+          return false;
+        }
+      }
+
+      return true;
+    }
+
+    //! Test if image instance represents a valid serialization of a 3d object.
+    /**
+       Return \c true if the image instance represents a valid serialization of a 3d object, and \c false otherwise.
+       \param is_full_check : Boolean telling if full checking of the instance must be performed.
+       \param[out] error_message : C-string to contain the error message, if the test does not succeed.
+       \note
+       - Set \c is_full_checking to \c false to speed-up the 3d object checking. In this case, only the size of
+         each 3d object component is checked.
+       - Size of the string \c error_message should be at least 128-bytes long, to be able to contain the error message.
+       \see is_object3d(),
+           object3dtoCImg3d(const CImgList<tp>&,const CImgList<tc>&,const to&),
+           draw_object3d(),
+           display_object3d().
+    **/
+    bool is_CImg3d(const bool is_full_check=true, char *const error_message=0) const {
+      if (error_message) *error_message = 0;
+
+      // Check instance dimension and header.
+      if (_width!=1 || _height<8 || _depth!=1 || _spectrum!=1) {
+        if (error_message) std::sprintf(error_message,
+                                        "CImg3d has invalid dimensions (%u,%u,%u,%u)",
+                                        _width,_height,_depth,_spectrum);
+        return false;
+      }
+      const T *ptrs = _data, *const ptre = end();
+      if (!_is_CImg3d(*(ptrs++),'C') || !_is_CImg3d(*(ptrs++),'I') || !_is_CImg3d(*(ptrs++),'m') ||
+          !_is_CImg3d(*(ptrs++),'g') || !_is_CImg3d(*(ptrs++),'3') || !_is_CImg3d(*(ptrs++),'d')) {
+        if (error_message) std::sprintf(error_message,
+                                        "CImg3d header not found");
+        return false;
+      }
+      const unsigned int
+        nb_points = cimg::float2uint((float)*(ptrs++)),
+        nb_primitives = cimg::float2uint((float)*(ptrs++));
+
+      // Check consistency of vertex data.
+      if (!nb_points) {
+        if (nb_primitives) {
+          if (error_message) std::sprintf(error_message,
+                                          "CImg3d has no vertices but %u primitives",
+                                          nb_primitives);
+          return false;
+        }
+        if (ptrs!=ptre) {
+          if (error_message) std::sprintf(error_message,
+                                          "CImg3d (%u,%u) is empty but contains %u byte%s more than expected",
+                                          nb_points,nb_primitives,(unsigned int)(ptre-ptrs),(ptre-ptrs)>1?"s":"");
+          return false;
+        }
+        return true;
+      }
+      ptrs+=3*nb_points;
+      if (ptrs>ptre) {
+        if (error_message) std::sprintf(error_message,
+                                        "CImg3d (%u,%u) has incomplete vertex data",
+                                        nb_points,nb_primitives);
+        return false;
+      }
+      if (!is_full_check) return true;
+
+      // Check consistency of primitive data.
+      if (ptrs==ptre) {
+        if (error_message) std::sprintf(error_message,
+                                        "CImg3d (%u,%u) has no primitive data",
+                                        nb_points,nb_primitives);
+        return false;
+      }
+      for (unsigned int p = 0; p<nb_primitives; ++p) {
+        const unsigned int nb_inds = (unsigned int)*(ptrs++);
+        switch (nb_inds) {
+        case 1 : { // Point.
+          const unsigned int i0 = cimg::float2uint((float)*(ptrs++));
+          if (i0>=nb_points) {
+            if (error_message) std::sprintf(error_message,
+                                            "CImg3d (%u,%u) refers to invalid vertex indice %u in point primitive %u",
+                                            nb_points,nb_primitives,i0,p);
+            return false;
+          }
+        } break;
+        case 5 : { // Sphere.
+          const unsigned int
+            i0 = cimg::float2uint((float)*(ptrs++)),
+            i1 = cimg::float2uint((float)*(ptrs++));
+          ptrs+=3;
+          if (i0>=nb_points || i1>=nb_points) {
+            if (error_message) std::sprintf(error_message,
+                                            "CImg3d (%u,%u) refers to invalid vertex indices (%u,%u) in sphere primitive %u",
+                                            nb_points,nb_primitives,i0,i1,p);
+            return false;
+          }
+        } break;
+        case 2 : case 6 : { // Segment.
+          const unsigned int
+            i0 = cimg::float2uint((float)*(ptrs++)),
+            i1 = cimg::float2uint((float)*(ptrs++));
+          if (nb_inds==6) ptrs+=4;
+          if (i0>=nb_points || i1>=nb_points) {
+            if (error_message) std::sprintf(error_message,
+                                            "CImg3d (%u,%u) refers to invalid vertex indices (%u,%u) in segment primitive %u",
+                                            nb_points,nb_primitives,i0,i1,p);
+            return false;
+          }
+        } break;
+        case 3 : case 9 : { // Triangle.
+          const unsigned int
+            i0 = cimg::float2uint((float)*(ptrs++)),
+            i1 = cimg::float2uint((float)*(ptrs++)),
+            i2 = cimg::float2uint((float)*(ptrs++));
+          if (nb_inds==9) ptrs+=6;
+          if (i0>=nb_points || i1>=nb_points || i2>=nb_points) {
+            if (error_message) std::sprintf(error_message,
+                                            "CImg3d (%u,%u) refers to invalid vertex indices (%u,%u,%u) in triangle primitive %u",
+                                            nb_points,nb_primitives,i0,i1,i2,p);
+            return false;
+          }
+        } break;
+        case 4 : case 12 : { // Quadrangle.
+          const unsigned int
+            i0 = cimg::float2uint((float)*(ptrs++)),
+            i1 = cimg::float2uint((float)*(ptrs++)),
+            i2 = cimg::float2uint((float)*(ptrs++)),
+            i3 = cimg::float2uint((float)*(ptrs++));
+          if (nb_inds==12) ptrs+=8;
+          if (i0>=nb_points || i1>=nb_points || i2>=nb_points || i3>=nb_points) {
+            if (error_message) std::sprintf(error_message,
+                                            "CImg3d (%u,%u) refers to invalid vertex indices (%u,%u,%u,%u) in quadrangle primitive %u",
+                                            nb_points,nb_primitives,i0,i1,i2,i3,p);
+            return false;
+          }
+        } break;
+        default :
+          if (error_message) std::sprintf(error_message,
+                                          "CImg3d (%u,%u) has invalid primitive %u of size %u",
+                                          nb_points,nb_primitives,p,nb_inds);
+          return false;
+        }
+        if (ptrs>ptre) {
+          if (error_message) std::sprintf(error_message,
+                                          "CImg3d (%u,%u) has incomplete primitive data for primitive %u",
+                                          nb_points,nb_primitives,p);
+          return false;
+        }
+      }
+
+      // Check consistency of color data.
+      if (ptrs==ptre) {
+        if (error_message) std::sprintf(error_message,
+                                        "CImg3d (%u,%u) has no color/texture data",
+                                        nb_points,nb_primitives);
+        return false;
+      }
+      for (unsigned int c = 0; c<nb_primitives; ++c) {
+        if ((int)*(ptrs++)!=-128) ptrs+=2;
+        else if ((ptrs+=3)<ptre) {
+          const unsigned int w = (unsigned int)*(ptrs-3), h = (unsigned int)*(ptrs-2), s = (unsigned int)*(ptrs-1);
+          if (!h && !s) {
+            if (w>=c) {
+              if (error_message) std::sprintf(error_message,
+                                              "CImg3d (%u,%u) refers to invalid shared sprite/texture indice %u for primitive %u",
+                                              nb_points,nb_primitives,w,c);
+              return false;
+            }
+          } else ptrs+=w*h*s;
+        }
+        if (ptrs>ptre) {
+          if (error_message) std::sprintf(error_message,
+                                          "CImg3d (%u,%u) has incomplete color/texture data for primitive %u",
+                                          nb_points,nb_primitives,c);
+          return false;
+        }
+      }
+
+      // Check consistency of opacity data.
+      if (ptrs==ptre) {
+        if (error_message) std::sprintf(error_message,
+                                        "CImg3d (%u,%u) has no opacity data",
+                                        nb_points,nb_primitives);
+        return false;
+      }
+      for (unsigned int o = 0; o<nb_primitives; ++o) {
+        if ((int)*(ptrs++)==-128 && (ptrs+=3)<ptre) {
+          const unsigned int w = (unsigned int)*(ptrs-3), h = (unsigned int)*(ptrs-2), s = (unsigned int)*(ptrs-1);
+          if (!h && !s) {
+            if (w>=o) {
+              if (error_message) std::sprintf(error_message,
+                                              "CImg3d (%u,%u) refers to invalid shared opacity indice %u for primitive %u",
+                                              nb_points,nb_primitives,w,o);
+              return false;
+            }
+          } else ptrs+=w*h*s;
+        }
+        if (ptrs>ptre) {
+          if (error_message) std::sprintf(error_message,
+                                          "CImg3d (%u,%u) has incomplete opacity data for primitive %u",
+                                          nb_points,nb_primitives,o);
+          return false;
+        }
+      }
+
+      // Check end of data.
+      if (ptrs<ptre) {
+        if (error_message) std::sprintf(error_message,
+                                        "CImg3d (%u,%u) contains %u byte%s more than expected",
+                                        nb_points,nb_primitives,(unsigned int)(ptre-ptrs),(ptre-ptrs)>1?"s":"");
+        return false;
+      }
+      return true;
+    }
+
+    static bool _is_CImg3d(const T val, const char c) {
+      return val>=(T)c && val<(T)(c+1);
+    }
+
+    //@}
+    //-------------------------------------
+    //
+    //! \name Mathematical Functions
+    //@{
+    //-------------------------------------
+
+    // Define the math formula parser/compiler and evaluator.
+    struct _cimg_math_parser {
+      CImgList<charT> label;
+      CImgList<uintT> code;
+      CImg<uintT> level, opcode;
+      CImg<doubleT> mem;
+      CImg<charT> expr;
+      const CImg<T>& reference;
+      CImg<Tdouble> reference_stats;
+      unsigned int mempos, result;
+      const char *const calling_function;
+#define _cimg_mp_return(x) { *se = saved_char; return x; }
+#define _cimg_mp_opcode0(op) _cimg_mp_return(opcode0(op));
+#define _cimg_mp_opcode1(op,i1) _cimg_mp_return(opcode1(op,i1));
+#define _cimg_mp_opcode2(op,i1,i2) { const unsigned int _i1 = i1, _i2 = i2; _cimg_mp_return(opcode2(op,_i1,_i2)); }
+#define _cimg_mp_opcode3(op,i1,i2,i3) { const unsigned int _i1 = i1, _i2 = i2, _i3 = i3; _cimg_mp_return(opcode3(op,_i1,_i2,_i3)); }
+#define _cimg_mp_opcode5(op,i1,i2,i3,i4,i5) { const unsigned int _i1 = i1, _i2 = i2, _i3 = i3, _i4 = i4, _i5 = i5; \
+          _cimg_mp_return(opcode5(op,_i1,_i2,_i3,_i4,_i5)); }
+
+      // Constructor - Destructor.
+      _cimg_math_parser(const CImg<T>& img, const char *const expression, const char *const funcname=0):
+        reference(img),calling_function(funcname?funcname:"cimg_math_parser") {
+        unsigned int l = 0;
+        if (expression) {
+          l = std::strlen(expression);
+          expr.assign(expression,l+1);
+          if (*expr._data) {
+            char *d = expr._data;
+            for (const char *s = expr._data; *s || (bool)(*d=0); ++s) if (*s!=' ') *(d++) = *s;
+            l = d - expr._data;
+          }
+        }
+        if (!l) throw CImgArgumentException("[_cimg_math_parser] "
+                                            "CImg<%s>::%s() : Empty specified expression.",
+                                            pixel_type(),calling_function);
+
+        int lv = 0; // Count parenthesis level of expression.
+        level.assign(l);
+        unsigned int *pd = level._data;
+        for (const char *ps = expr._data; *ps && lv>=0; ++ps) *(pd++) = (unsigned int)(*ps=='('?lv++:*ps==')'?--lv:lv);
+        if (lv!=0) {
+          throw CImgArgumentException("[_cimg_math_parser] "
+                                      "CImg<%s>::%s() : Unbalanced parentheses in specified expression '%s'.",
+                                      pixel_type(),calling_function,
+                                      expr._data);
+        }
+        // Init constant values.
+        mem.assign(512);
+        label.assign(512);
+        mem[0] = 0;
+        mem[1] = 1;
+        mem[2] = (double)reference._width;
+        mem[3] = (double)reference._height;
+        mem[4] = (double)reference._depth;
+        mem[5] = (double)reference._spectrum;
+        mem[6] = cimg::PI;
+        mem[7] = std::exp(1.0); // Then [8] = x, [9] = y, [10] = z, [11] = c
+        mempos = 12;
+        result = compile(expr._data,expr._data+l); // Compile formula into a serie of opcodes.
+      }
+
+      // Insert code instructions.
+      unsigned int opcode0(const char op) {
+        if (mempos>=mem._width) mem.resize(-200,1,1,1,0);
+        const unsigned int pos = mempos++;
+        CImg<uintT>::vector(op,pos).move_to(code);
+        return pos;
+      }
+
+      unsigned int opcode1(const char op, const unsigned int arg1) {
+        if (mempos>=mem._width) mem.resize(-200,1,1,1,0);
+        const unsigned int pos = mempos++;
+        CImg<uintT>::vector(op,pos,arg1).move_to(code);
+        return pos;
+      }
+
+      unsigned int opcode2(const char op, const unsigned int arg1, const unsigned int arg2) {
+        if (mempos>=mem._width) mem.resize(-200,1,1,1,0);
+        const unsigned int pos = mempos++;
+        CImg<uintT>::vector(op,pos,arg1,arg2).move_to(code);
+        return pos;
+      }
+
+      unsigned int opcode3(const char op, const unsigned int arg1, const unsigned int arg2, const unsigned int arg3) {
+        if (mempos>=mem._width) mem.resize(-200,1,1,1,0);
+        const unsigned int pos = mempos++;
+        CImg<uintT>::vector(op,pos,arg1,arg2,arg3).move_to(code);
+        return pos;
+      }
+
+      unsigned int opcode5(const char op, const unsigned int arg1, const unsigned int arg2, const unsigned int arg3,
+                           const unsigned int arg4, const unsigned int arg5) {
+        if (mempos>=mem._width) mem.resize(-200,1,1,1,0);
+        const unsigned int pos = mempos++;
+        CImg<uintT>::vector(op,pos,arg1,arg2,arg3,arg4,arg5).move_to(code);
+        return pos;
+      }
+
+      // Compilation procedure.
+      unsigned int compile(char *const ss, char *const se) {
+        if (!ss || se<=ss || !*ss) {
+          throw CImgArgumentException("[_cimg_math_parser] "
+                                      "CImg<%s>::%s() : Missing item in specified expression '%s'.",
+                                      pixel_type(),calling_function,
+                                      expr._data);
+        }
+        char
+          *const se1 = se-1, *const se2 = se-2, *const se3 = se-3, *const se4 = se-4,
+          *const ss1 = ss+1, *const ss2 = ss+2, *const ss3 = ss+3, *const ss4 = ss+4,
+          *const ss5 = ss+5, *const ss6 = ss+6, *const ss7 = ss+7;
+        const char saved_char = *se; *se = 0;
+        const unsigned int clevel = level[ss-expr._data], clevel1 = clevel+1;
+        if (*se1==';') return compile(ss,se1);
+
+        // Look for a single value, variable or variable assignment.
+        char end = 0, sep = 0; double val = 0;
+        const int nb = std::sscanf(ss,"%lf%c%c",&val,&sep,&end);
+        if (nb==1) {
+          if (val==0) _cimg_mp_return(0);
+          if (val==1) _cimg_mp_return(1);
+          if (mempos>=mem._width) mem.resize(-200,1,1,1,0);
+          const unsigned int pos = mempos++;
+          mem[pos] = val;
+          _cimg_mp_return(pos);
+        }
+        if (nb==2 && sep=='%') {
+          if (val==0) _cimg_mp_return(0);
+          if (val==100) _cimg_mp_return(1);
+          if (mempos>=mem._width) mem.resize(-200,1,1,1,0);
+          const unsigned int pos = mempos++;
+          mem[pos] = val/100;
+          _cimg_mp_return(pos);
+        }
+        if (ss1==se) switch (*ss) {
+          case 'w' : _cimg_mp_return(2); case 'h' : _cimg_mp_return(3); case 'd' : _cimg_mp_return(4); case 's' : _cimg_mp_return(5);
+          case 'x' : _cimg_mp_return(8); case 'y' : _cimg_mp_return(9); case 'z' : _cimg_mp_return(10); case 'c' : _cimg_mp_return(11);
+          case 'e' : _cimg_mp_return(7);
+          case 'u' : case '?' : _cimg_mp_opcode2(0,0,1);
+          case 'g' : _cimg_mp_opcode0(1);
+          case 'i' : _cimg_mp_opcode0(2);
+          }
+        if (ss1==se1) {
+          if (*ss=='p' && *ss1=='i') _cimg_mp_return(6); // pi
+          if (*ss=='i') {
+            if (*ss1=='m') _cimg_mp_opcode0(57); // im
+            if (*ss1=='M') _cimg_mp_opcode0(58); // iM
+            if (*ss1=='a') _cimg_mp_opcode0(59); // ia
+            if (*ss1=='v') _cimg_mp_opcode0(60); // iv
+          }
+          if (*ss1=='m') {
+            if (*ss=='x') _cimg_mp_opcode0(61); // xm
+            if (*ss=='y') _cimg_mp_opcode0(62); // ym
+            if (*ss=='z') _cimg_mp_opcode0(63); // zm
+            if (*ss=='c') _cimg_mp_opcode0(64); // cm
+          }
+          if (*ss1=='M') {
+            if (*ss=='x') _cimg_mp_opcode0(65); // xM
+            if (*ss=='y') _cimg_mp_opcode0(66); // yM
+            if (*ss=='z') _cimg_mp_opcode0(67); // zM
+            if (*ss=='c') _cimg_mp_opcode0(68); // cM
+          }
+        }
+        if (ss3==se) {
+          if (*ss=='x' && *ss1=='/' && *ss2=='w') _cimg_mp_opcode0(3);
+          if (*ss=='y' && *ss1=='/' && *ss2=='h') _cimg_mp_opcode0(4);
+          if (*ss=='z' && *ss1=='/' && *ss2=='d') _cimg_mp_opcode0(5);
+          if (*ss=='c' && *ss1=='/' && *ss2=='s') _cimg_mp_opcode0(6);
+        }
+
+        // Look for variable declarations.
+        for (char *s = se2; s>ss; --s) if (*s==';' && level[s-expr._data]==clevel) { compile(ss,s); _cimg_mp_return(compile(s+1,se)); }
+        for (char *s = ss1, *ps = ss, *ns = ss2; s<se1; ++s, ++ps, ++ns)
+          if (*s=='=' && *ns!='=' && *ps!='=' && *ps!='>' && *ps!='<' && *ps!='!' && level[s-expr._data]==clevel) {
+             CImg<charT> variable_name(ss,s-ss+1); variable_name.back() = 0;
+             bool is_valid_name = true;
+             if ((*ss>='0' && *ss<='9') ||
+                 (s==ss+1 && (*ss=='x' || *ss=='y' || *ss=='z' || *ss=='c' ||
+                              *ss=='w' || *ss=='h' || *ss=='d' || *ss=='s' ||
+                              *ss=='e' || *ss=='u' || *ss=='g' || *ss=='i')) ||
+                 (s==ss+2 && ((*ss=='p' && *(ss+1)=='i') ||
+                              (*ss=='i' && (*(ss+1)=='m' || *(ss+1)=='M' || *(ss+1)=='a' || *(ss+1)=='v'))))) is_valid_name = false;
+             for (const char *ns = ss; ns<s; ++ns)
+               if ((*ns<'a' || *ns>'z') && (*ns<'A' || *ns>'Z') && (*ns<'0' || *ns>'9') && *ns!='_') {
+                 is_valid_name = false; break;
+               }
+             if (!is_valid_name) {
+               *se = saved_char;
+               if (!std::strcmp(variable_name,"x") || !std::strcmp(variable_name,"y") || !std::strcmp(variable_name,"z") ||
+                   !std::strcmp(variable_name,"c") || !std::strcmp(variable_name,"w") || !std::strcmp(variable_name,"h") ||
+                   !std::strcmp(variable_name,"d") || !std::strcmp(variable_name,"s") || !std::strcmp(variable_name,"e") ||
+                   !std::strcmp(variable_name,"u") || !std::strcmp(variable_name,"g") || !std::strcmp(variable_name,"i") ||
+                   !std::strcmp(variable_name,"pi") || !std::strcmp(variable_name,"im") || !std::strcmp(variable_name,"iM") ||
+                   !std::strcmp(variable_name,"ia") || !std::strcmp(variable_name,"iv"))
+                  throw CImgArgumentException("[_cimg_math_parser] "
+                                             "CImg<%s>::%s() : Invalid assignment of reserved variable name '%s' in specified expression '%s'.",
+                                             pixel_type(),calling_function,
+                                             variable_name._data,expr._data);
+               else
+                 throw CImgArgumentException("[_cimg_math_parser] "
+                                             "CImg<%s>::%s() : Invalid variable name '%s' in specified expression '%s'.",
+                                             pixel_type(),calling_function,
+                                             variable_name._data,expr._data);
+             }
+             for (unsigned int i = 0; i<mempos; ++i) // Check for existing variable with same name.
+               if (label[i]._data && !std::strcmp(variable_name,label[i])) {
+                 *se = saved_char;
+                 throw CImgArgumentException("[_cimg_math_parser] "
+                                             "CImg<%s>::%s() : Invalid multiple assignments of variable '%s' in specified expression '%s'.",
+                                             pixel_type(),calling_function,
+                                             variable_name._data,expr._data);
+               }
+             const unsigned int src_pos = compile(s+1,se);
+             if (mempos>=mem.size()) mem.resize(-200,1,1,1,0);
+             const unsigned int dest_pos = mempos++;
+             variable_name.move_to(label[dest_pos]);
+             CImg<uintT>::vector(7,dest_pos,src_pos).move_to(code);
+             _cimg_mp_return(dest_pos);
+           }
+
+        // Look for unary/binary operators. The operator precedences is defined as in C++.
+        for (char *s = se3, *ns = se2; s>ss; --s, --ns) if (*s=='|' && *ns=='|' && level[s-expr._data]==clevel) _cimg_mp_opcode2(8,compile(ss,s),compile(s+2,se));
+        for (char *s = se3, *ns = se2; s>ss; --s, --ns) if (*s=='&' && *ns=='&' && level[s-expr._data]==clevel) _cimg_mp_opcode2(9,compile(ss,s),compile(s+2,se));
+        for (char *s = se2; s>ss; --s) if (*s=='|' && level[s-expr._data]==clevel) _cimg_mp_opcode2(10,compile(ss,s),compile(s+1,se));
+        for (char *s = se2; s>ss; --s) if (*s=='&' && level[s-expr._data]==clevel) _cimg_mp_opcode2(11,compile(ss,s),compile(s+1,se));
+        for (char *s = se3, *ns = se2; s>ss; --s, --ns) if (*s=='!' && *ns=='=' && level[s-expr._data]==clevel) _cimg_mp_opcode2(12,compile(ss,s),compile(s+2,se));
+        for (char *s = se3, *ns = se2; s>ss; --s, --ns) if (*s=='=' && *ns=='=' && level[s-expr._data]==clevel) _cimg_mp_opcode2(13,compile(ss,s),compile(s+2,se));
+        for (char *s = se3, *ns = se2; s>ss; --s, --ns) if (*s=='<' && *ns=='=' && level[s-expr._data]==clevel) _cimg_mp_opcode2(14,compile(ss,s),compile(s+2,se));
+        for (char *s = se3, *ns = se2; s>ss; --s, --ns) if (*s=='>' && *ns=='=' && level[s-expr._data]==clevel) _cimg_mp_opcode2(15,compile(ss,s),compile(s+2,se));
+        for (char *s = se2, *ns = se1, *ps = se3; s>ss; --s, --ns, --ps)
+          if (*s=='<' && *ns!='<' && *ps!='<' && level[s-expr._data]==clevel) _cimg_mp_opcode2(16,compile(ss,s),compile(s+1,se));
+        for (char *s = se2, *ns = se1, *ps = se3; s>ss; --s, --ns, --ps)
+          if (*s=='>' && *ns!='>' && *ps!='>' && level[s-expr._data]==clevel) _cimg_mp_opcode2(17,compile(ss,s),compile(s+1,se));
+        for (char *s = se3, *ns = se2; s>ss; --s, --ns) if (*s=='<' && *ns=='<' && level[s-expr._data]==clevel) _cimg_mp_opcode2(18,compile(ss,s),compile(s+2,se));
+        for (char *s = se3, *ns = se2; s>ss; --s, --ns) if (*s=='>' && *ns=='>' && level[s-expr._data]==clevel) _cimg_mp_opcode2(19,compile(ss,s),compile(s+2,se));
+        for (char *s = se2, *ps = se3; s>ss; --s, --ps)
+          if (*s=='+' && *ps!='-' && *ps!='+' && *ps!='*' && *ps!='/' && *ps!='%' && *ps!='&' && *ps!='|' && *ps!='^' && *ps!='!' && *ps!='~' &&
+              (*ps!='e' || !(ps>ss && (*(ps-1)=='.' || (*(ps-1)>='0' && *(ps-1)<='9')))) && level[s-expr._data]==clevel)
+            _cimg_mp_opcode2(21,compile(ss,s),compile(s+1,se));
+        for (char *s = se2, *ps = se3; s>ss; --s, --ps)
+          if (*s=='-' && *ps!='-' && *ps!='+' && *ps!='*' && *ps!='/' && *ps!='%' && *ps!='&' && *ps!='|' && *ps!='^' && *ps!='!' && *ps!='~' &&
+              (*ps!='e' || !(ps>ss && (*(ps-1)=='.' || (*(ps-1)>='0' && *(ps-1)<='9')))) && level[s-expr._data]==clevel)
+            _cimg_mp_opcode2(20,compile(ss,s),compile(s+1,se));
+        for (char *s = se2; s>ss; --s) if (*s=='*' && level[s-expr._data]==clevel) _cimg_mp_opcode2(22,compile(ss,s),compile(s+1,se));
+        for (char *s = se2; s>ss; --s) if (*s=='/' && level[s-expr._data]==clevel) _cimg_mp_opcode2(23,compile(ss,s),compile(s+1,se));
+        for (char *s = se2, *ns = se1; s>ss; --s, --ns)
+          if (*s=='%' && *ns!='^' && level[s-expr._data]==clevel)
+            _cimg_mp_opcode2(24,compile(ss,s),compile(s+1,se));
+        if (ss<se1) {
+          if (*ss=='+') _cimg_mp_return(compile(ss1,se));
+          if (*ss=='-') _cimg_mp_opcode1(26,compile(ss1,se));
+          if (*ss=='!') _cimg_mp_opcode1(27,compile(ss1,se));
+          if (*ss=='~') _cimg_mp_opcode1(28,compile(ss1,se));
+        }
+        for (char *s = se2; s>ss; --s) if (*s=='^' && level[s-expr._data]==clevel) _cimg_mp_opcode2(25,compile(ss,s),compile(s+1,se));
+
+        // Look for a function call or a parenthesis.
+        if (*se1==')') {
+          if (*ss=='(') _cimg_mp_return(compile(ss1,se1));
+          if (!std::strncmp(ss,"sin(",4)) _cimg_mp_opcode1(29,compile(ss4,se1));
+          if (!std::strncmp(ss,"cos(",4)) _cimg_mp_opcode1(30,compile(ss4,se1));
+          if (!std::strncmp(ss,"tan(",4)) _cimg_mp_opcode1(31,compile(ss4,se1));
+          if (!std::strncmp(ss,"asin(",5)) _cimg_mp_opcode1(32,compile(ss5,se1));
+          if (!std::strncmp(ss,"acos(",5)) _cimg_mp_opcode1(33,compile(ss5,se1));
+          if (!std::strncmp(ss,"atan(",5)) _cimg_mp_opcode1(34,compile(ss5,se1));
+          if (!std::strncmp(ss,"sinh(",5)) _cimg_mp_opcode1(35,compile(ss5,se1));
+          if (!std::strncmp(ss,"cosh(",5)) _cimg_mp_opcode1(36,compile(ss5,se1));
+          if (!std::strncmp(ss,"tanh(",5)) _cimg_mp_opcode1(37,compile(ss5,se1));
+          if (!std::strncmp(ss,"log10(",6)) _cimg_mp_opcode1(38,compile(ss6,se1));
+          if (!std::strncmp(ss,"log2(",5)) _cimg_mp_opcode1(71,compile(ss5,se1));
+          if (!std::strncmp(ss,"log(",4)) _cimg_mp_opcode1(39,compile(ss4,se1));
+          if (!std::strncmp(ss,"exp(",4)) _cimg_mp_opcode1(40,compile(ss4,se1));
+          if (!std::strncmp(ss,"sqrt(",5)) _cimg_mp_opcode1(41,compile(ss5,se1));
+          if (!std::strncmp(ss,"sign(",5)) _cimg_mp_opcode1(42,compile(ss5,se1));
+          if (!std::strncmp(ss,"abs(",4)) _cimg_mp_opcode1(43,compile(ss4,se1));
+          if (!std::strncmp(ss,"atan2(",6)) {
+            char *s1 = ss6; while (s1<se2 && (*s1!=',' || level[s1-expr._data]!=clevel1)) ++s1;
+            _cimg_mp_opcode2(44,compile(ss6,s1),compile(s1+1,se1));
+          }
+          if (!std::strncmp(ss,"if(",3)) {
+            char *s1 = ss3; while (s1<se4 && (*s1!=',' || level[s1-expr._data]!=clevel1)) ++s1;
+            char *s2 = s1+1; while (s2<se2 && (*s2!=',' || level[s2-expr._data]!=clevel1)) ++s2;
+            _cimg_mp_opcode3(45,compile(ss3,s1),compile(s1+1,s2),compile(s2+1,se1));
+          }
+          if (!std::strncmp(ss,"round(",6)) {
+            unsigned int value = 0, round = 1, direction = 0;
+            char *s1 = ss6; while (s1<se2 && (*s1!=',' || level[s1-expr._data]!=clevel1)) ++s1;
+            value = compile(ss6,s1==se2?++s1:s1);
+            if (s1<se1) {
+              char *s2 = s1+1; while (s2<se2 && (*s2!=',' || level[s2-expr._data]!=clevel1)) ++s2;
+              round = compile(s1+1,s2==se2?++s2:s2);
+              if (s2<se1) direction = compile(s2+1,se1);
+            }
+            _cimg_mp_opcode3(46,value,round,direction);
+          }
+          if (!std::strncmp(ss,"?(",2) || !std::strncmp(ss,"u(",2)) {
+            if (*ss2==')') _cimg_mp_opcode2(0,0,1);
+            char *s1 = ss2; while (s1<se1 && (*s1!=',' || level[s1-expr._data]!=clevel1)) ++s1;
+            if (s1<se1) _cimg_mp_opcode2(0,compile(ss2,s1),compile(s1+1,se1));
+            _cimg_mp_opcode2(0,0,compile(ss2,s1));
+          }
+          if (!std::strncmp(ss,"i(",2)) {
+            if (*ss2==')') _cimg_mp_return(0);
+            unsigned int indx = 8, indy = 9, indz = 10, indc = 11, borders = 0;
+            if (ss2!=se1) {
+              char *s1 = ss2; while (s1<se2 && (*s1!=',' || level[s1-expr._data]!=clevel1)) ++s1;
+              indx = compile(ss2,s1==se2?++s1:s1);
+              if (s1<se1) {
+                char *s2 = s1+1; while (s2<se2 && (*s2!=',' || level[s2-expr._data]!=clevel1)) ++s2;
+                indy = compile(s1+1,s2==se2?++s2:s2);
+                if (s2<se1) {
+                  char *s3 = s2+1; while (s3<se2 && (*s3!=',' || level[s3-expr._data]!=clevel1)) ++s3;
+                  indz = compile(s2+1,s3==se2?++s3:s3);
+                  if (s3<se1) {
+                    char *s4 = s3+1; while (s4<se2 && (*s4!=',' || level[s4-expr._data]!=clevel1)) ++s4;
+                    indc = compile(s3+1,s4==se2?++s4:s4);
+                    if (s4<se1) borders = compile(s4+1,se1);
+                  }
+                }
+              }
+            }
+            _cimg_mp_opcode5(47,indx,indy,indz,indc,borders);
+          }
+          if (!std::strncmp(ss,"min(",4) || !std::strncmp(ss,"max(",4)) {
+            CImgList<uintT> opcode;
+            if (mempos>=mem.size()) mem.resize(-200,1,1,1,0);
+            const unsigned int pos = mempos++;
+            CImg<uintT>::vector(ss[1]=='i'?48:49,pos).move_to(opcode);
+            for (char *s = ss4; s<se; ++s) {
+              char *ns = s; while (ns<se && (*ns!=',' || level[ns-expr._data]!=clevel1) && (*ns!=')' || level[ns-expr._data]!=clevel)) ++ns;
+              CImg<uintT>::vector(compile(s,ns)).move_to(opcode);
+              s = ns;
+            }
+            (opcode>'y').move_to(code);
+            _cimg_mp_return(pos);
+          }
+          if (!std::strncmp(ss,"arg(",4)) {
+            CImgList<uintT> opcode;
+            if (mempos>=mem.size()) mem.resize(-200,1,1,1,0);
+            const unsigned int pos = mempos++;
+            CImg<uintT>::vector(69,pos).move_to(opcode);
+            for (char *s = ss4; s<se; ++s) {
+              char *ns = s; while (ns<se && (*ns!=',' || level[ns-expr._data]!=clevel1) && (*ns!=')' || level[ns-expr._data]!=clevel)) ++ns;
+              CImg<uintT>::vector(compile(s,ns)).move_to(opcode);
+              s = ns;
+            }
+            (opcode>'y').move_to(code);
+            _cimg_mp_return(pos);
+          }
+          if (!std::strncmp(ss,"narg(",5)) {
+            if (*ss5==')') _cimg_mp_return(0);
+            unsigned int nb_args = 0;
+            for (char *s = ss5; s<se; ++s) {
+              char *ns = s; while (ns<se && (*ns!=',' || level[ns-expr._data]!=clevel1) && (*ns!=')' || level[ns-expr._data]!=clevel)) ++ns;
+              ++nb_args; s = ns;
+            }
+            if (nb_args==0 || nb_args==1) _cimg_mp_return(nb_args);
+            if (mempos>=mem.size()) mem.resize(-200,1,1,1,0);
+            const unsigned int pos = mempos++;
+            mem[pos] = nb_args;
+            _cimg_mp_return(pos);
+          }
+          if (!std::strncmp(ss,"isval(",6)) {
+            char sep = 0, end = 0; double val = 0;
+            if (std::sscanf(ss6,"%lf%c%c",&val,&sep,&end)==2 && sep==')') _cimg_mp_return(1);
+            _cimg_mp_return(0);
+          }
+          if (!std::strncmp(ss,"isnan(",6)) _cimg_mp_opcode1(50,compile(ss6,se1));
+          if (!std::strncmp(ss,"isinf(",6)) _cimg_mp_opcode1(51,compile(ss6,se1));
+          if (!std::strncmp(ss,"isint(",6)) _cimg_mp_opcode1(52,compile(ss6,se1));
+          if (!std::strncmp(ss,"isbool(",7)) _cimg_mp_opcode1(53,compile(ss7,se1));
+          if (!std::strncmp(ss,"rol(",4) || !std::strncmp(ss,"ror(",4)) {
+            unsigned int value = 0, nb = 1;
+            char *s1 = ss4; while (s1<se2 && (*s1!=',' || level[s1-expr._data]!=clevel1)) ++s1;
+            value = compile(ss4,s1==se2?++s1:s1);
+            if (s1<se1) {
+              char *s2 = s1+1; while (s2<se2 && (*s2!=',' || level[s2-expr._data]!=clevel1)) ++s2;
+              nb = compile(s1+1,se1);
+            }
+            _cimg_mp_opcode2(*ss2=='l'?54:55,value,nb);
+          }
+
+          if (!std::strncmp(ss,"sinc(",5)) _cimg_mp_opcode1(56,compile(ss5,se1));
+          if (!std::strncmp(ss,"int(",4)) _cimg_mp_opcode1(70,compile(ss4,se1));
+        }
+
+        // No known item found, assuming this is an already initialized variable.
+        CImg<charT> variable_name(ss,se-ss+1); variable_name.back() = 0;
+        for (unsigned int i = 0; i<mempos; ++i) if (label[i]._data && !std::strcmp(variable_name,label[i])) _cimg_mp_return(i);
+        *se = saved_char;
+        throw CImgArgumentException("[_cimg_math_parser] "
+                                    "CImg<%s>::%s() : Invalid item '%s' in specified expression '%s'.\n",
+                                    pixel_type(),calling_function,
+                                    variable_name._data,expr._data);
+        return 0;
+      }
+
+      // Evaluation functions, known by the parser.
+      double mp_u() {
+        return mem[opcode(2)] + cimg::rand()*(mem[opcode(3)]-mem[opcode(2)]);
+      }
+      double mp_g() {
+        return cimg::grand();
+      }
+      double mp_i() {
+        return (double)reference.atXYZC((int)mem[8],(int)mem[9],(int)mem[10],(int)mem[11],0);
+      }
+      double mp_xw() {
+        return mem[8]/reference.width();
+      }
+      double mp_yh() {
+        return mem[9]/reference.height();
+      }
+      double mp_zd() {
+        return mem[10]/reference.depth();
+      }
+      double mp_cs() {
+        return mem[11]/reference.spectrum();
+      }
+      double mp_equal() {
+        return mem[opcode[2]];
+      }
+      double mp_logical_and() {
+        return (double)((bool)mem[opcode(2)] && (bool)mem[opcode(3)]);
+      }
+      double mp_logical_or() {
+        return (double)((bool)mem[opcode(2)] || (bool)mem[opcode(3)]);
+      }
+      double mp_infeq() {
+        return (double)(mem[opcode(2)]<=mem[opcode(3)]);
+      }
+      double mp_supeq() {
+        return (double)(mem[opcode(2)]>=mem[opcode(3)]);
+      }
+      double mp_noteq() {
+        return (double)(mem[opcode(2)]!=mem[opcode(3)]);
+      }
+      double mp_eqeq() {
+        return (double)(mem[opcode(2)]==mem[opcode(3)]);
+      }
+      double mp_inf() {
+        return (double)(mem[opcode(2)]<mem[opcode(3)]);
+      }
+      double mp_sup() {
+        return (double)(mem[opcode(2)]>mem[opcode(3)]);
+      }
+      double mp_add() {
+        return mem[opcode(2)] + mem[opcode(3)];
+      }
+      double mp_sub() {
+        return mem[opcode(2)] - mem[opcode(3)];
+      }
+      double mp_mul() {
+        return mem[opcode(2)] * mem[opcode(3)];
+      }
+      double mp_div() {
+        return mem[opcode(2)] / mem[opcode(3)];
+      }
+      double mp_minus() {
+        return -mem[opcode(2)];
+      }
+      double mp_not() {
+        return !mem[opcode(2)];
+      }
+      double mp_logical_not() {
+        return !mem[opcode(2)];
+      }
+      double mp_bitwise_not() {
+        return ~(unsigned long)mem[opcode(2)];
+      }
+      double mp_modulo() {
+        return cimg::mod(mem[opcode(2)],mem[opcode(3)]);
+      }
+      double mp_bitwise_and() {
+        return ((unsigned long)mem[opcode(2)] & (unsigned long)mem[opcode(3)]);
+      }
+      double mp_bitwise_or() {
+        return ((unsigned long)mem[opcode(2)] | (unsigned long)mem[opcode(3)]);
+      }
+      double mp_pow() {
+        return std::pow(mem[opcode(2)],mem[opcode(3)]);
+      }
+      double mp_sin() {
+        return std::sin(mem[opcode(2)]);
+      }
+      double mp_cos() {
+        return std::cos(mem[opcode(2)]);
+      }
+      double mp_tan() {
+        return std::tan(mem[opcode(2)]);
+      }
+      double mp_asin() {
+        return std::asin(mem[opcode(2)]);
+      }
+      double mp_acos() {
+        return std::acos(mem[opcode(2)]);
+      }
+      double mp_atan() {
+        return std::atan(mem[opcode(2)]);
+      }
+      double mp_sinh() {
+        return std::sinh(mem[opcode(2)]);
+      }
+      double mp_cosh() {
+        return std::cosh(mem[opcode(2)]);
+      }
+      double mp_tanh() {
+        return std::tanh(mem[opcode(2)]);
+      }
+      double mp_log10() {
+        return std::log10(mem[opcode(2)]);
+      }
+      double mp_log2() {
+        return cimg::log2(mem[opcode(2)]);
+      }
+      double mp_log() {
+        return std::log(mem[opcode(2)]);
+      }
+      double mp_exp() {
+        return std::exp(mem[opcode(2)]);
+      }
+      double mp_sqrt() {
+        return std::sqrt(mem[opcode(2)]);
+      }
+      double mp_sign() {
+        return cimg::sign(mem[opcode(2)]);
+      }
+      double mp_abs() {
+        return cimg::abs(mem[opcode(2)]);
+      }
+      double mp_atan2() {
+        return std::atan2(mem[opcode(2)],mem[opcode(3)]);
+      }
+      double mp_if() {
+        return mem[opcode(2)]?mem[opcode(3)]:mem[opcode(4)];
+      }
+      double mp_round() {
+        return cimg::round(mem[opcode(2)],mem[opcode(3)],(int)mem[opcode(4)]);
+      }
+      double mp_ixyzc() {
+        const int b = (int)mem[opcode(6)];
+        if (b==2) return (double)reference.atXYZC(cimg::mod((int)mem[opcode(2)],reference.width()),
+                                                  cimg::mod((int)mem[opcode(3)],reference.height()),
+                                                  cimg::mod((int)mem[opcode(4)],reference.depth()),
+                                                  cimg::mod((int)mem[opcode(5)],reference.spectrum()));
+        if (b==1) return (double)reference.atXYZC((int)mem[opcode(2)],
+                                                  (int)mem[opcode(3)],
+                                                  (int)mem[opcode(4)],
+                                                  (int)mem[opcode(5)]);
+        return (double)reference.atXYZC((int)mem[opcode(2)],
+                                        (int)mem[opcode(3)],
+                                        (int)mem[opcode(4)],
+                                        (int)mem[opcode(5)],0);
+      }
+      double mp_min() {
+        double val = mem[opcode(2)];
+        for (unsigned int i = 3; i<opcode._height; ++i) val = cimg::min(val,mem[opcode(i)]);
+        return val;
+      }
+      double mp_max() {
+        double val = mem[opcode(2)];
+        for (unsigned int i = 3; i<opcode._height; ++i) val = cimg::max(val,mem[opcode(i)]);
+        return val;
+      }
+      double mp_isnan() {
+        const double val = mem[opcode(2)];
+        return !(val==val);
+      }
+      double mp_isinf() {
+        const double val = mem[opcode(2)];
+        return val==(val+1);
+      }
+      double mp_isint() {
+        const double val = mem[opcode(2)];
+        return (double)(int)val==val;
+      }
+      double mp_isbool() {
+        const double val = mem[opcode(2)];
+        return (val==0.0 || val==1.0);
+      }
+      double mp_rol() {
+        return cimg::rol(mem[opcode(2)],(unsigned int)mem[opcode(3)]);
+      }
+      double mp_ror() {
+        return cimg::ror(mem[opcode(2)],(unsigned int)mem[opcode(3)]);
+      }
+      double mp_lsl() {
+        return (long)mem[opcode(2)]<<(unsigned int)mem[opcode(3)];
+      }
+      double mp_lsr() {
+        return (long)mem[opcode(2)]>>(unsigned int)mem[opcode(3)];
+      }
+      double mp_sinc() {
+        return cimg::sinc(mem[opcode(2)]);
+      }
+      double mp_im() {
+        if (!reference_stats) reference.get_stats().move_to(reference_stats);
+        return reference_stats?reference_stats[0]:0;
+      }
+      double mp_iM() {
+        if (!reference_stats) reference.get_stats().move_to(reference_stats);
+        return reference_stats?reference_stats[1]:0;
+      }
+      double mp_ia() {
+        if (!reference_stats) reference.get_stats().move_to(reference_stats);
+        return reference_stats?reference_stats[2]:0;
+      }
+      double mp_iv() {
+        if (!reference_stats) reference.get_stats().move_to(reference_stats);
+        return reference_stats?reference_stats[3]:0;
+      }
+      double mp_xm() {
+        if (!reference_stats) reference.get_stats().move_to(reference_stats);
+        return reference_stats?reference_stats[4]:0;
+      }
+      double mp_ym() {
+        if (!reference_stats) reference.get_stats().move_to(reference_stats);
+        return reference_stats?reference_stats[5]:0;
+      }
+      double mp_zm() {
+        if (!reference_stats) reference.get_stats().move_to(reference_stats);
+        return reference_stats?reference_stats[6]:0;
+      }
+      double mp_cm() {
+        if (!reference_stats) reference.get_stats().move_to(reference_stats);
+        return reference_stats?reference_stats[7]:0;
+      }
+      double mp_xM() {
+        if (!reference_stats) reference.get_stats().move_to(reference_stats);
+        return reference_stats?reference_stats[8]:0;
+      }
+      double mp_yM() {
+        if (!reference_stats) reference.get_stats().move_to(reference_stats);
+        return reference_stats?reference_stats[9]:0;
+      }
+      double mp_zM() {
+        if (!reference_stats) reference.get_stats().move_to(reference_stats);
+        return reference_stats?reference_stats[10]:0;
+      }
+      double mp_cM() {
+        if (!reference_stats) reference.get_stats().move_to(reference_stats);
+        return reference_stats?reference_stats[11]:0;
+      }
+      double mp_arg() {
+        const int _ind = (int)mem[opcode(2)];
+        const unsigned int nb_args = opcode._height-2, ind = _ind<0?_ind+nb_args:(unsigned int)_ind;
+        if (ind>=nb_args) return 0;
+        return mem[opcode(ind+2)];
+      }
+      double mp_int() {
+        return (double)(long)mem[opcode(2)];
+      }
+
+      // Evaluation procedure, with image data.
+      double eval(const double x, const double y, const double z, const double c) {
+        typedef double (_cimg_math_parser::*mp_func)();
+        const mp_func mp_funcs[] = {
+          &_cimg_math_parser::mp_u,            // 0
+          &_cimg_math_parser::mp_g,            // 1
+          &_cimg_math_parser::mp_i,            // 2
+          &_cimg_math_parser::mp_xw,           // 3
+          &_cimg_math_parser::mp_yh,           // 4
+          &_cimg_math_parser::mp_zd,           // 5
+          &_cimg_math_parser::mp_cs,           // 6
+          &_cimg_math_parser::mp_equal,        // 7
+          &_cimg_math_parser::mp_logical_or,   // 8
+          &_cimg_math_parser::mp_logical_and,  // 9
+          &_cimg_math_parser::mp_bitwise_or,   // 10
+          &_cimg_math_parser::mp_bitwise_and,  // 11
+          &_cimg_math_parser::mp_noteq,        // 12
+          &_cimg_math_parser::mp_eqeq,         // 13
+          &_cimg_math_parser::mp_infeq,        // 14
+          &_cimg_math_parser::mp_supeq,        // 15
+          &_cimg_math_parser::mp_inf,          // 16
+          &_cimg_math_parser::mp_sup,          // 17
+          &_cimg_math_parser::mp_lsl,          // 18
+          &_cimg_math_parser::mp_lsr,          // 19
+          &_cimg_math_parser::mp_sub,          // 20
+          &_cimg_math_parser::mp_add,          // 21
+          &_cimg_math_parser::mp_mul,          // 22
+          &_cimg_math_parser::mp_div,          // 23
+          &_cimg_math_parser::mp_modulo,       // 24
+          &_cimg_math_parser::mp_pow,          // 25
+          &_cimg_math_parser::mp_minus,        // 26
+          &_cimg_math_parser::mp_logical_not,  // 27
+          &_cimg_math_parser::mp_bitwise_not,  // 28
+          &_cimg_math_parser::mp_sin,          // 29
+          &_cimg_math_parser::mp_cos,          // 30
+          &_cimg_math_parser::mp_tan,          // 31
+          &_cimg_math_parser::mp_asin,         // 32
+          &_cimg_math_parser::mp_acos,         // 33
+          &_cimg_math_parser::mp_atan,         // 34
+          &_cimg_math_parser::mp_sinh,         // 35
+          &_cimg_math_parser::mp_cosh,         // 36
+          &_cimg_math_parser::mp_tanh,         // 37
+          &_cimg_math_parser::mp_log10,        // 38
+          &_cimg_math_parser::mp_log,          // 39
+          &_cimg_math_parser::mp_exp,          // 40
+          &_cimg_math_parser::mp_sqrt,         // 41
+          &_cimg_math_parser::mp_sign,         // 42
+          &_cimg_math_parser::mp_abs,          // 43
+          &_cimg_math_parser::mp_atan2,        // 44
+          &_cimg_math_parser::mp_if,           // 45
+          &_cimg_math_parser::mp_round,        // 46
+          &_cimg_math_parser::mp_ixyzc,        // 47
+          &_cimg_math_parser::mp_min,          // 48
+          &_cimg_math_parser::mp_max,          // 49
+          &_cimg_math_parser::mp_isnan,        // 50
+          &_cimg_math_parser::mp_isinf,        // 51
+          &_cimg_math_parser::mp_isint,        // 52
+          &_cimg_math_parser::mp_isbool,       // 53
+          &_cimg_math_parser::mp_rol,          // 54
+          &_cimg_math_parser::mp_ror,          // 55
+          &_cimg_math_parser::mp_sinc,         // 56
+          &_cimg_math_parser::mp_im,           // 57
+          &_cimg_math_parser::mp_iM,           // 58
+          &_cimg_math_parser::mp_ia,           // 59
+          &_cimg_math_parser::mp_iv,           // 60
+          &_cimg_math_parser::mp_xm,           // 61
+          &_cimg_math_parser::mp_ym,           // 62
+          &_cimg_math_parser::mp_zm,           // 63
+          &_cimg_math_parser::mp_cm,           // 64
+          &_cimg_math_parser::mp_xM,           // 65
+          &_cimg_math_parser::mp_yM,           // 66
+          &_cimg_math_parser::mp_zM,           // 67
+          &_cimg_math_parser::mp_cM,           // 68
+          &_cimg_math_parser::mp_arg,          // 69
+          &_cimg_math_parser::mp_int,          // 70
+          &_cimg_math_parser::mp_log2          // 71
+        };
+
+        if (!mem) return 0;
+        mem[8] = x; mem[9] = y; mem[10] = z; mem[11] = c;
+        opcode._is_shared = true; opcode._width = opcode._depth = opcode._spectrum = 1;
+        cimglist_for(code,l) {
+          const CImg<uintT> &op = code[l];
+          opcode._data = op._data; opcode._height = op._height;  // Allows to avoid parameter passing to evaluation functions.
+          mem[opcode(1)] = (this->*mp_funcs[opcode[0]])();
+        }
+        return mem[result];
+      }
+    };
+
+    //! Compute the square value of each pixel value.
+    /**
+       Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its square value \f$I_{(x,y,z,c)}^2\f$.
+       \note
+       - The \inplace of this method statically casts the computed values to the pixel type \c T.
+       - The \newinstance returns a \c CImg<float> image, if the pixel type \c T is \e not float-valued.
+       \par Example
+       \code
+       const CImg<float> img("reference.jpg");
+       (img,img.get_sqr().normalize(0,255)).display();
+       \endcode
+       \image html ref_sqr.jpg
+       \see get_sqr() const,
+           sqrt(),
+           pow(),
+           exp(),
+           log(),
+           log2(),
+           log10().
+    **/
+    CImg<T>& sqr() {
+      cimg_for(*this,ptrd,T) { const T val = *ptrd; *ptrd = (T)(val*val); };
+      return *this;
+    }
+
+    //! Compute the square value of each pixel value \newinstance.
+    CImg<Tfloat> get_sqr() const {
+      return CImg<Tfloat>(*this,false).sqr();
+    }
+
+    //! Compute the square root of each pixel value.
+    /**
+       Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its square root \f$\sqrt{I_{(x,y,z,c)}}\f$.
+       \note
+       - The \inplace of this method statically casts the computed values to the pixel type \c T.
+       - The \newinstance returns a \c CImg<float> image, if the pixel type \c T is \e not float-valued.
+       \par Example
+       \code
+       const CImg<float> img("reference.jpg");
+       (img,img.get_sqrt().normalize(0,255)).display();
+       \endcode
+       \image html ref_sqrt.jpg
+       \see get_sqrt() const,,
+           sqr(),
+           pow(),
+           exp(),
+           log(),
+           log2(),
+           log10().
+    **/
+    CImg<T>& sqrt() {
+      cimg_for(*this,ptrd,T) *ptrd = (T)std::sqrt((double)*ptrd);
+      return *this;
+    }
+
+    //! Compute the square root of each pixel value \newinstance.
+    CImg<Tfloat> get_sqrt() const {
+      return CImg<Tfloat>(*this,false).sqrt();
+    }
+
+    //! Compute the exponential of each pixel value.
+    /**
+       Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its exponential \f$e^{I_{(x,y,z,c)}}\f$.
+       \note
+       - The \inplace of this method statically casts the computed values to the pixel type \c T.
+       - The \newinstance returns a \c CImg<float> image, if the pixel type \c T is \e not float-valued.
+       \see get_exp() const,
+           sqr(),
+           sqrt(),
+           pow(),
+           log(),
+           log2(),
+           log10().
+    **/
+    CImg<T>& exp() {
+      cimg_for(*this,ptrd,T) *ptrd = (T)std::exp((double)*ptrd);
+      return *this;
+    }
+
+    //! Compute the exponential of each pixel value \newinstance.
+    CImg<Tfloat> get_exp() const {
+      return CImg<Tfloat>(*this,false).exp();
+    }
+
+    //! Compute the logarithm of each pixel value.
+    /**
+       Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its logarithm \f$log_e(I_{(x,y,z,c)})\f$.
+       \note
+       - The \inplace of this method statically casts the computed values to the pixel type \c T.
+       - The \newinstance returns a \c CImg<float> image, if the pixel type \c T is \e not float-valued.
+       \see get_log() const,
+           sqr(),
+           sqrt(),
+           pow(),
+           exp(),
+           log2(),
+           log10().
+    **/
+    CImg<T>& log() {
+      cimg_for(*this,ptrd,T) *ptrd = (T)std::log((double)*ptrd);
+      return *this;
+    }
+
+    //! Compute the logarithm of each pixel value \newinstance.
+    CImg<Tfloat> get_log() const {
+      return CImg<Tfloat>(*this,false).log();
+    }
+
+    //! Compute the base-2 logarithm of each pixel value.
+    /**
+       Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its base-2 logarithm \f$log_{2}(I_{(x,y,z,c)})\f$.
+       \note
+       - The \inplace of this method statically casts the computed values to the pixel type \c T.
+       - The \newinstance returns a \c CImg<float> image, if the pixel type \c T is \e not float-valued.
+       \see get_log2(),
+           sqr(),
+           sqrt(),
+           pow(),
+           exp(),
+           log(),
+           log10().
+    **/
+    CImg<T>& log2() {
+      cimg_for(*this,ptrd,T) *ptrd = (T)cimg::log2((double)*ptrd);
+      return *this;
+    }
+
+    //! Compute the base-10 logarithm of each pixel value \newinstance.
+    CImg<Tfloat> get_log2() const {
+      return CImg<Tfloat>(*this,false).log2();
+    }
+
+    //! Compute the base-10 logarithm of each pixel value.
+    /**
+       Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its base-10 logarithm \f$log_{10}(I_{(x,y,z,c)})\f$.
+       \note
+       - The \inplace of this method statically casts the computed values to the pixel type \c T.
+       - The \newinstance returns a \c CImg<float> image, if the pixel type \c T is \e not float-valued.
+       \see get_log10(),
+           sqr(),
+           sqrt(),
+           pow(),
+           exp(),
+           log(),
+           log2().
+    **/
+    CImg<T>& log10() {
+      cimg_for(*this,ptrd,T) *ptrd = (T)std::log10((double)*ptrd);
+      return *this;
+    }
+
+    //! Compute the base-10 logarithm of each pixel value \newinstance.
+    CImg<Tfloat> get_log10() const {
+      return CImg<Tfloat>(*this,false).log10();
+    }
+
+    //! Compute the absolute value of each pixel value.
+    /**
+       Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its absolute value \f$|I_{(x,y,z,c)}|\f$.
+       \note
+       - The \inplace of this method statically casts the computed values to the pixel type \c T.
+       - The \newinstance returns a \c CImg<float> image, if the pixel type \c T is \e not float-valued.
+       \see get_abs(),
+           sign().
+    **/
+    CImg<T>& abs() {
+      cimg_for(*this,ptrd,T) *ptrd = cimg::abs(*ptrd);
+      return *this;
+    }
+
+    //! Compute the absolute value of each pixel value \newinstance.
+    CImg<Tfloat> get_abs() const {
+      return CImg<Tfloat>(*this,false).abs();
+    }
+
+    //! Compute the sign of each pixel value.
+    /**
+       Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its sign \f$sign(I_{(x,y,z,c)})\f$.
+       \note
+       - The sign is set to :
+         - \c 1 if pixel value is strictly positive.
+         - \c -1 if pixel value is strictly negative.
+         - \c 0 if pixel value is equal to \c 0.
+       - The \inplace of this method statically casts the computed values to the pixel type \c T.
+       - The \newinstance returns a \c CImg<float> image, if the pixel type \c T is \e not float-valued.
+       \see get_sign(),
+           abs().
+    **/
+    CImg<T>& sign() {
+      cimg_for(*this,ptrd,T) *ptrd = cimg::sign(*ptrd);
+      return *this;
+    }
+
+    //! Compute the sign of each pixel value \newinstance.
+    CImg<Tfloat> get_sign() const {
+      return CImg<Tfloat>(*this,false).sign();
+    }
+
+    //! Compute the cosine of each pixel value.
+    /**
+       Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its cosine \f$cos(I_{(x,y,z,c)})\f$.
+       \note
+       - Pixel values are regarded as being in \e radian.
+       - The \inplace of this method statically casts the computed values to the pixel type \c T.
+       - The \newinstance returns a \c CImg<float> image, if the pixel type \c T is \e not float-valued.
+       \see get_cos(),
+           sin(),
+           sinc(),
+           tan().
+    **/
+    CImg<T>& cos() {
+      cimg_for(*this,ptrd,T) *ptrd = (T)std::cos((double)*ptrd);
+      return *this;
+    }
+
+    //! Compute the cosine of each pixel value \newinstance.
+    CImg<Tfloat> get_cos() const {
+      return CImg<Tfloat>(*this,false).cos();
+    }
+
+    //! Compute the sine of each pixel value.
+    /**
+       Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its sine \f$sin(I_{(x,y,z,c)})\f$.
+       \note
+       - Pixel values are regarded as being in \e radian.
+       - The \inplace of this method statically casts the computed values to the pixel type \c T.
+       - The \newinstance returns a \c CImg<float> image, if the pixel type \c T is \e not float-valued.
+       \see get_sin(),
+           cos(),
+           sinc(),
+           tan().
+    **/
+    CImg<T>& sin() {
+      cimg_for(*this,ptrd,T) *ptrd = (T)std::sin((double)*ptrd);
+      return *this;
+    }
+
+    //! Compute the sine of each pixel value \newinstance.
+    CImg<Tfloat> get_sin() const {
+      return CImg<Tfloat>(*this,false).sin();
+    }
+
+    //! Compute the sinc of each pixel value.
+    /**
+       Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its sinc \f$sinc(I_{(x,y,z,c)})\f$.
+       \note
+       - Pixel values are regarded as being exin \e radian.
+       - The \inplace of this method statically casts the computed values to the pixel type \c T.
+       - The \newinstance returns a \c CImg<float> image, if the pixel type \c T is \e not float-valued.
+       \see get_sinc(),
+           cos(),
+           sin(),
+           tan().
+    **/
+    CImg<T>& sinc() {
+      cimg_for(*this,ptrd,T) *ptrd = (T)cimg::sinc((double)*ptrd);
+      return *this;
+    }
+
+    //! Compute the sinc of each pixel value \newinstance.
+    CImg<Tfloat> get_sinc() const {
+      return CImg<Tfloat>(*this,false).sinc();
+    }
+
+    //! Compute the tangent of each pixel value.
+    /**
+       Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its tangent \f$tan(I_{(x,y,z,c)})\f$.
+       \note
+       - Pixel values are regarded as being exin \e radian.
+       - The \inplace of this method statically casts the computed values to the pixel type \c T.
+       - The \newinstance returns a \c CImg<float> image, if the pixel type \c T is \e not float-valued.
+       \see get_tan(),
+           cos(),
+           sin(),
+           sinc().
+    **/
+    CImg<T>& tan() {
+      cimg_for(*this,ptrd,T) *ptrd = (T)std::tan((double)*ptrd);
+      return *this;
+    }
+
+    //! Compute the tangent of each pixel value \newinstance.
+    CImg<Tfloat> get_tan() const {
+      return CImg<Tfloat>(*this,false).tan();
+    }
+
+    //! Compute the hyperbolic cosine of each pixel value.
+    /**
+       Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its hyperbolic cosine \f$cosh(I_{(x,y,z,c)})\f$.
+       \note
+       - The \inplace of this method statically casts the computed values to the pixel type \c T.
+       - The \newinstance returns a \c CImg<float> image, if the pixel type \c T is \e not float-valued.
+       \see get_cosh(),
+           sinh(),
+           tanh().
+    **/
+    CImg<T>& cosh() {
+      cimg_for(*this,ptrd,T) *ptrd = (T)std::cosh((double)*ptrd);
+      return *this;
+    }
+
+    //! Compute the hyperbolic cosine of each pixel value \newinstance.
+    CImg<Tfloat> get_cosh() const {
+      return CImg<Tfloat>(*this,false).cosh();
+    }
+
+    //! Compute the hyperbolic sine of each pixel value.
+    /**
+       Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its hyperbolic sine \f$sinh(I_{(x,y,z,c)})\f$.
+       \note
+       - The \inplace of this method statically casts the computed values to the pixel type \c T.
+       - The \newinstance returns a \c CImg<float> image, if the pixel type \c T is \e not float-valued.
+       \see get_sinh(),
+           cosh(),
+           tanh().
+    **/
+    CImg<T>& sinh() {
+      cimg_for(*this,ptrd,T) *ptrd = (T)std::sinh((double)*ptrd);
+      return *this;
+    }
+
+    //! Compute the hyperbolic sine of each pixel value \newinstance.
+    CImg<Tfloat> get_sinh() const {
+      return CImg<Tfloat>(*this,false).sinh();
+    }
+
+    //! Compute the hyperbolic tangent of each pixel value.
+    /**
+       Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its hyperbolic tangent \f$tanh(I_{(x,y,z,c)})\f$.
+       \note
+       - The \inplace of this method statically casts the computed values to the pixel type \c T.
+       - The \newinstance returns a \c CImg<float> image, if the pixel type \c T is \e not float-valued.
+       \see get_tanh(),
+           cosh(),
+           sinh().
+    **/
+    CImg<T>& tanh() {
+      cimg_for(*this,ptrd,T) *ptrd = (T)std::tanh((double)*ptrd);
+      return *this;
+    }
+
+    //! Compute the hyperbolic tangent of each pixel value \newinstance.
+    CImg<Tfloat> get_tanh() const {
+      return CImg<Tfloat>(*this,false).tanh();
+    }
+
+    //! Compute the arccosine of each pixel value.
+    /**
+       Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its arccosine \f$acos(I_{(x,y,z,c)})\f$.
+       \note
+       - The \inplace of this method statically casts the computed values to the pixel type \c T.
+       - The \newinstance returns a \c CImg<float> image, if the pixel type \c T is \e not float-valued.
+       \see get_acos(),
+           asin(),
+           atan().
+    **/
+    CImg<T>& acos() {
+      cimg_for(*this,ptrd,T) *ptrd = (T)std::acos((double)*ptrd);
+      return *this;
+    }
+
+    //! Compute the arccosine of each pixel value \newinstance.
+    CImg<Tfloat> get_acos() const {
+      return CImg<Tfloat>(*this,false).acos();
+    }
+
+    //! Compute the arcsine of each pixel value.
+    /**
+       Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its arcsine \f$asin(I_{(x,y,z,c)})\f$.
+       \note
+       - The \inplace of this method statically casts the computed values to the pixel type \c T.
+       - The \newinstance returns a \c CImg<float> image, if the pixel type \c T is \e not float-valued.
+       \see get_asin(),
+           acos(),
+           atan().
+    **/
+    CImg<T>& asin() {
+      cimg_for(*this,ptrd,T) *ptrd = (T)std::asin((double)*ptrd);
+      return *this;
+    }
+
+    //! Compute the arcsine of each pixel value \newinstance.
+    CImg<Tfloat> get_asin() const {
+      return CImg<Tfloat>(*this,false).asin();
+    }
+
+    //! Compute the arctangent of each pixel value.
+    /**
+       Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its arctangent \f$atan(I_{(x,y,z,c)})\f$.
+       \note
+       - The \inplace of this method statically casts the computed values to the pixel type \c T.
+       - The \newinstance returns a \c CImg<float> image, if the pixel type \c T is \e not float-valued.
+       \see get_atan(),
+           acos(),
+           asin().
+    **/
+    CImg<T>& atan() {
+      cimg_for(*this,ptrd,T) *ptrd = (T)std::atan((double)*ptrd);
+      return *this;
+    }
+
+    //! Compute the arctangent of each pixel value \newinstance.
+    CImg<Tfloat> get_atan() const {
+      return CImg<Tfloat>(*this,false).atan();
+    }
+
+    //! Compute the arctangent2 of each pixel value.
+    /**
+       Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its arctangent2 \f$atan2(I_{(x,y,z,c)})\f$.
+       \param img : The image whose pixel values specify the second argument of the \c atan2() function.
+       \note
+       - The \inplace of this method statically casts the computed values to the pixel type \c T.
+       - The \newinstance returns a \c CImg<float> image, if the pixel type \c T is \e not float-valued.
+       \par Example
+       \code
+       const CImg<float>
+          img_x(100,100,1,1,"x-w/2",false),   // Define an horizontal centered gradient, from '-width/2' to 'width/2'.
+          img_y(100,100,1,1,"y-h/2",false),   // Define a vertical centered gradient, from '-height/2' to 'height/2'.
+          img_atan2 = img_y.get_atan2(img_x); // Compute atan2(y,x) for each pixel value.
+       (img_x,img_y,img_atan2).display();
+       \endcode
+       \image html ref_atan2.jpg
+       \see get_atan2(),
+           atan().
+    **/
+    template<typename t>
+    CImg<T>& atan2(const CImg<t>& img) {
+      const unsigned int siz = size(), isiz = img.size();
+      if (siz && isiz) {
+        if (is_overlapped(img)) return atan2(+img);
+        T *ptrd = _data, *const ptre = _data + siz;
+        if (siz>isiz) for (unsigned int n = siz/isiz; n; --n)
+          for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs<ptrs_end; ++ptrd) *ptrd = (T)std::atan2((double)*ptrd,(double)*(ptrs++));
+        for (const t *ptrs = img._data; ptrd<ptre; ++ptrd) *ptrd = (T)std::atan2((double)*ptrd,(double)*(ptrs++));
+      }
+      return *this;
+    }
+
+    //! Compute the arctangent2 of each pixel value \newinstance.
+    template<typename t>
+    CImg<Tfloat> get_atan2(const CImg<t>& img) const {
+      return CImg<Tfloat>(*this,false).atan2(img);
+    }
+
+    //! In-place pointwise multiplication.
+    /**
+       Compute the pointwise multiplication between the image instance and the specified input image \c img.
+       \param img : Input image, as the second operand of the multiplication.
+       \note
+       - Similar to operator+=(const CImg<t>&), except that it performs a pointwise multiplication instead of an addition.
+       - It does \e not perform a \e matrix multiplication. For this purpose, use operator*=(const CImg<t>&) instead.
+       \par Example
+       \code
+       CImg<float>
+         img("reference.jpg"),
+         shade(img.width,img.height(),1,1,"-(x-w/2)^2-(y-h/2)^2",false);
+       shade.normalize(0,1);
+       (img,shade,img.get_mul(shade)).display();
+       \endcode
+       \image html ref_mul.jpg
+       \see get_mul(),
+           div(),
+           operator*=(const CImg<t>&).
+    **/
+    template<typename t>
+    CImg<T>& mul(const CImg<t>& img) {
+      const unsigned int siz = size(), isiz = img.size();
+      if (siz && isiz) {
+        if (is_overlapped(img)) return mul(+img);
+        T *ptrd = _data, *const ptre = _data + siz;
+        if (siz>isiz) for (unsigned int n = siz/isiz; n; --n)
+          for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs<ptrs_end; ++ptrd) *ptrd = (T)(*ptrd * *(ptrs++));
+        for (const t *ptrs = img._data; ptrd<ptre; ++ptrd) *ptrd = (T)(*ptrd * *(ptrs++));
+      }
+      return *this;
+    }
+
+    //! In-place pointwise multiplication \newinstance.
+    template<typename t>
+    CImg<_cimg_Tt> get_mul(const CImg<t>& img) const {
+      return CImg<_cimg_Tt>(*this,false).mul(img);
+    }
+
+    //! In-place pointwise division.
+    /**
+       Similar to mul(const CImg<t>&), except that it performs a pointwise division instead of a multiplication.
+       \see get_div(const CImg<t>&) const,
+           operator/=(const CImg<t>&)
+    **/
+    template<typename t>
+    CImg<T>& div(const CImg<t>& img) {
+      const unsigned int siz = size(), isiz = img.size();
+      if (siz && isiz) {
+        if (is_overlapped(img)) return div(+img);
+        T *ptrd = _data, *const ptre = _data + siz;
+        if (siz>isiz) for (unsigned int n = siz/isiz; n; --n)
+          for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs<ptrs_end; ++ptrd) *ptrd = (T)(*ptrd / *(ptrs++));
+        for (const t *ptrs = img._data; ptrd<ptre; ++ptrd) *ptrd = (T)(*ptrd / *(ptrs++));
+      }
+      return *this;
+    }
+
+    //! In-place pointwise division \newinstance.
+    template<typename t>
+    CImg<_cimg_Tt> get_div(const CImg<t>& img) const {
+      return CImg<_cimg_Tt>(*this,false).div(img);
+    }
+
+    //! Raise each pixel value to a specified power.
+    /**
+       Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its power \f$I_{(x,y,z,c)}^p\f$.
+       \param p : Used exponent.
+       \note
+       - The \inplace of this method statically casts the computed values to the pixel type \c T.
+       - The \newinstance returns a \c CImg<float> image, if the pixel type \c T is \e not float-valued.
+       \par Example
+       \code
+       const CImg<float>
+         img0("reference.jpg"),           // Load reference color image.
+         img1 = (img0/255).pow(1.8)*=255, // Compute gamma correction, with gamma = 1.8.
+         img2 = (img0/255).pow(0.5)*=255; // Compute gamma correction, with gamma = 0.5.
+       (img0,img1,img2).display();
+       \endcode
+       \image html ref_pow.jpg
+       \see get_pow(double) const,
+           pow(const char*),
+           pow(const CImg<t>&),
+           sqr(),
+           sqrt(),
+           exp(),
+           log(),
+           log2(),
+           log10().
+    **/
+    CImg<T>& pow(const double p) {
+      if (p==0) return fill(1);
+      if (p==0.5) { cimg_for(*this,ptrd,T) { const T val = *ptrd; *ptrd = (T)std::sqrt((double)val); } return *this; }
+      if (p==1) return *this;
+      if (p==2) { cimg_for(*this,ptrd,T) { const T val = *ptrd; *ptrd = val*val; } return *this; }
+      if (p==3) { cimg_for(*this,ptrd,T) { const T val = *ptrd; *ptrd = val*val*val; } return *this; }
+      if (p==4) { cimg_for(*this,ptrd,T) { const T val = *ptrd; *ptrd = val*val*val*val; } return *this; }
+      cimg_for(*this,ptrd,T) *ptrd = (T)std::pow((double)*ptrd,p);
+      return *this;
+    }
+
+    //! Raise each pixel value to a specified power \newinstance.
+    CImg<Tfloat> get_pow(const double p) const {
+      return CImg<Tfloat>(*this,false).pow(p);
+    }
+
+    //! Raise each pixel value to a power, specified from an expression.
+    /**
+       Similar to operator+=(const char*), except it performs a pointwise exponentiation instead of an addition.
+       \see get_pow(const char*) const,
+           pow(double),
+           pow(CImg<t>&).
+    **/
+    CImg<T>& pow(const char *const expression) {
+      const unsigned int omode = cimg::exception_mode();
+      cimg::exception_mode() = 0;
+      try {
+        const CImg<T> _base = std::strstr(expression,"i(")?+*this:CImg<T>(), &base = _base?_base:*this;
+        _cimg_math_parser mp(base,expression,"pow");
+        T *ptrd = _data;
+        cimg_forXYZC(*this,x,y,z,c) { *ptrd = (T)std::pow((double)*ptrd,mp.eval(x,y,z,c)); ++ptrd; }
+      } catch (CImgException&) {
+        CImg<Tfloat> values(_width,_height,_depth,_spectrum);
+        try {
+          values.fill(expression,true);
+        } catch (CImgException&) {
+          cimg::exception_mode() = omode;
+          values.load(expression);
+        }
+        pow(values);
+      }
+      cimg::exception_mode() = omode;
+      return *this;
+    }
+
+    //! Raise each pixel value to a power, specified from an expression \newinstance.
+    CImg<Tfloat> get_pow(const char *const expression) const {
+      return CImg<Tfloat>(*this,false).pow(expression);
+    }
+
+    //! Raise each pixel value to a power, pointwisely specified from another image.
+    /**
+       Similar to operator+=(const CImg<t>& img), except that it performs an exponentiation instead of an addition.
+       \see get_pow(const CImg<t>&) const,
+           pow(double),
+           pow(const char*).
+    **/
+    template<typename t>
+    CImg<T>& pow(const CImg<t>& img) {
+      const unsigned int siz = size(), isiz = img.size();
+      if (siz && isiz) {
+        if (is_overlapped(img)) return pow(+img);
+        T *ptrd = _data, *const ptre = _data + siz;
+        if (siz>isiz) for (unsigned int n = siz/isiz; n; --n)
+          for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs<ptrs_end; ++ptrd) *ptrd = (T)std::pow((double)*ptrd,(double)(*(ptrs++)));
+        for (const t *ptrs = img._data; ptrd<ptre; ++ptrd) *ptrd = (T)std::pow((double)*ptrd,(double)(*(ptrs++)));
+      }
+      return *this;
+    }
+
+    //! Raise each pixel value to a power, pointwisely specified from another image \newinstance.
+    template<typename t>
+    CImg<Tfloat> get_pow(const CImg<t>& img) const {
+      return CImg<Tfloat>(*this,false).pow(img);
+    }
+
+    //! Compute the bitwise left rotation of each pixel value.
+    /**
+       Similar to operator<<=(unsigned int), except that it performs a left rotation instead of a left shift.
+       \see get_rol(unsigned int) const,
+           rol(const char*),
+           rol(const CImg<t>&),
+           operator<<=(unsigned int),
+           operator>>=(unsigned int).
+    **/
+    CImg<T>& rol(const unsigned int n=1) {
+      cimg_for(*this,ptrd,T) *ptrd = (T)cimg::rol(*ptrd,n);
+      return *this;
+    }
+
+    //! Compute the bitwise left rotation of each pixel value \newinstance.
+    CImg<T> get_rol(const unsigned int n=1) const {
+      return (+*this).rol(n);
+    }
+
+    //! Compute the bitwise left rotation of each pixel value.
+    /**
+       Similar to operator<<=(const char*), except that it performs a left rotation instead of a left shift.
+       \see get_rol(const char*) const,
+           rol(unsigned int),
+           rol(const CImg<t>&),
+           operator<<=(const char*),
+           operator>>=(const char*).
+    **/
+    CImg<T>& rol(const char *const expression) {
+      const unsigned int omode = cimg::exception_mode();
+      cimg::exception_mode() = 0;
+      try {
+        const CImg<T> _base = std::strstr(expression,"i(")?+*this:CImg<T>(), &base = _base?_base:*this;
+        _cimg_math_parser mp(base,expression,"rol");
+        T *ptrd = _data;
+        cimg_forXYZC(*this,x,y,z,c) { *ptrd = (T)cimg::rol(*ptrd,(unsigned int)mp.eval(x,y,z,c)); ++ptrd; }
+      } catch (CImgException&) {
+        CImg<Tfloat> values(_width,_height,_depth,_spectrum);
+        try {
+          values.fill(expression,true);
+        } catch (CImgException&) {
+          cimg::exception_mode() = omode;
+          values.load(expression);
+        }
+        rol(values);
+      }
+      cimg::exception_mode() = omode;
+      return *this;
+    }
+
+    //! Compute the bitwise left rotation of each pixel value \newinstance.
+    CImg<T> get_rol(const char *const expression) const {
+      return (+*this).rol(expression);
+    }
+
+    //! Compute the bitwise left rotation of each pixel value.
+    /**
+       Similar to operator<<=(const CImg<t>&), except that it performs a left rotation instead of a left shift.
+       \see get_rol(const CImg<t>&) const,
+           rol(unsigned int),
+           rol(const char*),
+           operator<<=(const CImg<t>&),
+           operator>>=(const CImg<t>&).
+    **/
+    template<typename t>
+    CImg<T>& rol(const CImg<t>& img) {
+      const unsigned int siz = size(), isiz = img.size();
+      if (siz && isiz) {
+        if (is_overlapped(img)) return rol(+img);
+        T *ptrd = _data, *const ptre = _data + siz;
+        if (siz>isiz) for (unsigned int n = siz/isiz; n; --n)
+          for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs<ptrs_end; ++ptrd) *ptrd = (T)cimg::rol(*ptrd,(unsigned int)(*(ptrs++)));
+        for (const t *ptrs = img._data; ptrd<ptre; ++ptrd) *ptrd = (T)cimg::rol(*ptrd,(unsigned int)(*(ptrs++)));
+      }
+      return *this;
+    }
+
+    //! Compute the bitwise left rotation of each pixel value \newinstance.
+    template<typename t>
+    CImg<T> get_rol(const CImg<t>& img) const {
+      return (+*this).rol(img);
+    }
+
+    //! Compute the bitwise right rotation of each pixel value.
+    /**
+       Similar to operator>>=(unsigned int), except that it performs a right rotation instead of a right shift.
+       \see get_ror(unsigned int) const,
+           ror(const char*),
+           ror(const CImg<t>&),
+           operator<<=(unsigned int),
+           operator>>=(unsigned int).
+    **/
+    CImg<T>& ror(const unsigned int n=1) {
+      cimg_for(*this,ptrd,T) *ptrd = (T)cimg::ror(*ptrd,n);
+      return *this;
+    }
+
+    //! Compute the bitwise right rotation of each pixel value \newinstance.
+    CImg<T> get_ror(const unsigned int n=1) const {
+      return (+*this).ror(n);
+    }
+
+    //! Compute the bitwise right rotation of each pixel value.
+    /**
+       Similar to operator>>=(const char*), except that it performs a right rotation instead of a right shift.
+       \see get_ror(const char*) const,
+           ror(unsigned int),
+           ror(const CImg<t>&),
+           operator<<=(const char*),
+           operator>>=(const char*).
+    **/
+    CImg<T>& ror(const char *const expression) {
+      const unsigned int omode = cimg::exception_mode();
+      cimg::exception_mode() = 0;
+      try {
+        const CImg<T> _base = std::strstr(expression,"i(")?+*this:CImg<T>(), &base = _base?_base:*this;
+        _cimg_math_parser mp(base,expression,"ror");
+        T *ptrd = _data;
+        cimg_forXYZC(*this,x,y,z,c) { *ptrd = (T)cimg::ror(*ptrd,(unsigned int)mp.eval(x,y,z,c)); ++ptrd; }
+      } catch (CImgException&) {
+        CImg<Tfloat> values(_width,_height,_depth,_spectrum);
+        try {
+          values.fill(expression,true);
+        } catch (CImgException&) {
+          cimg::exception_mode() = omode;
+          values.load(expression);
+        }
+        ror(values);
+      }
+      cimg::exception_mode() = omode;
+      return *this;
+    }
+
+    //! Compute the bitwise right rotation of each pixel value \newinstance.
+    CImg<T> get_ror(const char *const expression) const {
+      return (+*this).ror(expression);
+    }
+
+    //! Compute the bitwise right rotation of each pixel value.
+    /**
+       Similar to operator>>=(const CImg<t>&), except that it performs a right rotation instead of a right shift.
+       \see get_ror(const CImg<t>&),
+           ror(unsigned int),
+           ror(const char *),
+           operator<<=(const CImg<t>&),
+           operator>>=(const CImg<t>&).
+    **/
+    template<typename t>
+    CImg<T>& ror(const CImg<t>& img) {
+      const unsigned int siz = size(), isiz = img.size();
+      if (siz && isiz) {
+        if (is_overlapped(img)) return ror(+img);
+        T *ptrd = _data, *const ptre = _data + siz;
+        if (siz>isiz) for (unsigned int n = siz/isiz; n; --n)
+          for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs<ptrs_end; ++ptrd) *ptrd = (T)cimg::ror(*ptrd,(unsigned int)(*(ptrs++)));
+        for (const t *ptrs = img._data; ptrd<ptre; ++ptrd) *ptrd = (T)cimg::ror(*ptrd,(unsigned int)(*(ptrs++)));
+      }
+      return *this;
+    }
+
+    //! Compute the bitwise right rotation of each pixel value \newinstance.
+    template<typename t>
+    CImg<T> get_ror(const CImg<t>& img) const {
+      return (+*this).ror(img);
+    }
+
+    //! Pointwise min operator between an image and a value.
+    CImg<T>& min(const T val) {
+      cimg_for(*this,ptrd,T) *ptrd = cimg::min(*ptrd,val);
+      return *this;
+    }
+
+    CImg<T> get_min(const T val) const {
+      return (+*this).min(val);
+    }
+
+    //! Pointwise min operator between two images.
+    template<typename t>
+    CImg<T>& min(const CImg<t>& img) {
+      const unsigned int siz = size(), isiz = img.size();
+      if (siz && isiz) {
+        if (is_overlapped(img)) return min(+img);
+        T *ptrd = _data, *const ptre = _data + siz;
+        if (siz>isiz) for (unsigned int n = siz/isiz; n; --n)
+          for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs<ptrs_end; ++ptrd) *ptrd = cimg::min((T)*(ptrs++),*ptrd);
+        for (const t *ptrs = img._data; ptrd<ptre; ++ptrd) *ptrd = cimg::min((T)*(ptrs++),*ptrd);
+      }
+      return *this;
+    }
+
+    template<typename t>
+    CImg<_cimg_Tt> get_min(const CImg<t>& img) const {
+      return CImg<_cimg_Tt>(*this,false).min(img);
+    }
+
+    //! Pointwise min operator between an image and a string.
+    CImg<T>& min(const char *const expression) {
+      const unsigned int omode = cimg::exception_mode();
+      cimg::exception_mode() = 0;
+      try {
+        const CImg<T> _base = std::strstr(expression,"i(")?+*this:CImg<T>(), &base = _base?_base:*this;
+        _cimg_math_parser mp(base,expression,"min");
+        T *ptrd = _data;
+        cimg_forXYZC(*this,x,y,z,c) { *ptrd = (T)cimg::min(*ptrd,(T)mp.eval(x,y,z,c)); ++ptrd; }
+      } catch (CImgException&) {
+        CImg<T> values(_width,_height,_depth,_spectrum);
+        try {
+          values.fill(expression,true);
+        } catch (CImgException&) {
+          cimg::exception_mode() = omode;
+          values.load(expression);
+        }
+        min(values);
+      }
+      cimg::exception_mode() = omode;
+      return *this;
+    }
+
+    CImg<Tfloat> get_min(const char *const expression) const {
+      return CImg<Tfloat>(*this,false).min(expression);
+    }
+
+    //! Pointwise max operator between an image and a value.
+    CImg<T>& max(const T val) {
+      cimg_for(*this,ptrd,T) *ptrd = cimg::max(*ptrd,val);
+      return *this;
+    }
+
+    CImg<T> get_max(const T val) const {
+      return (+*this).max(val);
+    }
+
+    //! Pointwise max operator between two images.
+    template<typename t>
+    CImg<T>& max(const CImg<t>& img) {
+      const unsigned int siz = size(), isiz = img.size();
+      if (siz && isiz) {
+        if (is_overlapped(img)) return max(+img);
+        T *ptrd = _data, *const ptre = _data + siz;
+        if (siz>isiz) for (unsigned int n = siz/isiz; n; --n)
+          for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs<ptrs_end; ++ptrd) *ptrd = cimg::max((T)*(ptrs++),*ptrd);
+        for (const t *ptrs = img._data; ptrd<ptre; ++ptrd) *ptrd = cimg::max((T)*(ptrs++),*ptrd);
+      }
+      return *this;
+    }
+
+    template<typename t>
+    CImg<_cimg_Tt> get_max(const CImg<t>& img) const {
+      return CImg<_cimg_Tt>(*this,false).max(img);
+    }
+
+    //! Pointwise max operator between an image and a string.
+    CImg<T>& max(const char *const expression) {
+      const unsigned int omode = cimg::exception_mode();
+      cimg::exception_mode() = 0;
+      try {
+        const CImg<T> _base = std::strstr(expression,"i(")?+*this:CImg<T>(), &base = _base?_base:*this;
+        _cimg_math_parser mp(base,expression,"max");
+        T *ptrd = _data;
+        cimg_forXYZC(*this,x,y,z,c) { *ptrd = (T)cimg::max(*ptrd,(T)mp.eval(x,y,z,c)); ++ptrd; }
+      } catch (CImgException&) {
+        CImg<T> values(_width,_height,_depth,_spectrum);
+        try {
+          values.fill(expression,true);
+        } catch (CImgException&) {
+          cimg::exception_mode() = omode;
+          values.load(expression);
+        }
+        max(values);
+      }
+      cimg::exception_mode() = omode;
+      return *this;
+    }
+
+    CImg<Tfloat> get_max(const char *const expression) const {
+      return CImg<Tfloat>(*this,false).max(expression);
+    }
+
+    //! Return a reference to the minimum pixel value of the image instance
+    T& min() {
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "min() : Empty instance.",
+                                    cimg_instance);
+      T *ptr_min = _data;
+      T min_value = *ptr_min;
+      cimg_for(*this,ptrs,T) if (*ptrs<min_value) min_value = *(ptr_min=ptrs);
+      return *ptr_min;
+    }
+
+    const T& min() const {
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "min() : Empty instance.",
+                                    cimg_instance);
+      const T *ptr_min = _data;
+      T min_value = *ptr_min;
+      cimg_for(*this,ptrs,T) if (*ptrs<min_value) min_value = *(ptr_min=ptrs);
+      return *ptr_min;
+    }
+
+    //! Return a reference to the maximum pixel value of the image instance
+    T& max() {
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "max() : Empty instance.",
+                                    cimg_instance);
+      T *ptr_max = _data;
+      T max_value = *ptr_max;
+      cimg_for(*this,ptrs,T) if (*ptrs>max_value) max_value = *(ptr_max=ptrs);
+      return *ptr_max;
+    }
+
+    const T& max() const {
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "max() : Empty instance.",
+                                    cimg_instance);
+      const T *ptr_max = _data;
+      T max_value = *ptr_max;
+      cimg_for(*this,ptrs,T) if (*ptrs>max_value) max_value = *(ptr_max=ptrs);
+      return *ptr_max;
+    }
+
+    //! Return a reference to the minimum pixel value and return also the maximum pixel value.
+    template<typename t>
+    T& min_max(t& max_val) {
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "min_max() : Empty instance.",
+                                    cimg_instance);
+      T *ptr_min = _data;
+      T min_value = *ptr_min, max_value = min_value;
+      cimg_for(*this,ptrs,T) {
+        const T val = *ptrs;
+        if (val<min_value) { min_value = val; ptr_min = ptrs; }
+        if (val>max_value) max_value = val;
+      }
+      max_val = (t)max_value;
+      return *ptr_min;
+    }
+
+    template<typename t>
+    const T& min_max(t& max_val) const {
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "min_max() : Empty instance.",
+                                    cimg_instance);
+      const T *ptr_min = _data;
+      T min_value = *ptr_min, max_value = min_value;
+      cimg_for(*this,ptrs,T) {
+        const T val = *ptrs;
+        if (val<min_value) { min_value = val; ptr_min = ptrs; }
+        if (val>max_value) max_value = val;
+      }
+      max_val = (t)max_value;
+      return *ptr_min;
+    }
+
+    //! Return a reference to the maximum pixel value and return also the minimum pixel value.
+    template<typename t>
+    T& max_min(t& min_val) {
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "max_min() : Empty instance.",
+                                    cimg_instance);
+      T *ptr_max = _data;
+      T max_value = *ptr_max, min_value = max_value;
+      cimg_for(*this,ptrs,T) {
+        const T val = *ptrs;
+        if (val>max_value) { max_value = val; ptr_max = ptrs; }
+        if (val<min_value) min_value = val;
+      }
+      min_val = (t)min_value;
+      return *ptr_max;
+    }
+
+    template<typename t>
+    const T& max_min(t& min_val) const {
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "max_min() : Empty instance.",
+                                    cimg_instance);
+      const T *ptr_max = _data;
+      T max_value = *ptr_max, min_value = max_value;
+      cimg_for(*this,ptrs,T) {
+        const T val = *ptrs;
+        if (val>max_value) { max_value = val; ptr_max = ptrs; }
+        if (val<min_value) min_value = val;
+      }
+      min_val = (t)min_value;
+      return *ptr_max;
+    }
+
+    //! Return the kth smallest element of the image.
+    T kth_smallest(const unsigned int k) const {
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "kth_smallest() : Empty instance.",
+                                    cimg_instance);
+      CImg<T> arr(*this);
+      unsigned int 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 int 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 int 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;
+    }
+
+    //! Return the median value of the image.
+    T median() const {
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "median() : Empty instance.",
+                                    cimg_instance);
+      const unsigned int s = size();
+      const T res = kth_smallest(s>>1);
+      return (s%2)?res:((res+kth_smallest((s>>1)-1))/2);
+    }
+
+    //! Return the sum of all the pixel values in an image.
+    Tdouble sum() const {
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "sum() : Empty instance.",
+                                    cimg_instance);
+      Tdouble res = 0;
+      cimg_for(*this,ptrs,T) res+=(Tdouble)*ptrs;
+      return res;
+    }
+
+    //! Return the mean pixel value of the image instance.
+    Tdouble mean() const {
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "mean() : Empty instance.",
+                                    cimg_instance);
+      Tdouble res = 0;
+      cimg_for(*this,ptrs,T) res+=(Tdouble)*ptrs;
+      return res/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>
+    */
+    Tdouble variance(const unsigned int variance_method=1) const {
+      Tdouble foo;
+      return variance_mean(variance_method,foo);
+    }
+
+    //! Return the variance and the mean of the image.
+    template<typename t>
+    Tdouble variance_mean(const unsigned int variance_method, t& mean) const {
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "variance_mean() : Empty instance.",
+                                    cimg_instance);
+
+      Tdouble variance = 0, average = 0;
+      const unsigned int siz = size();
+      switch (variance_method) {
+      case 0 :{ // Least mean square (standard definition)
+        Tdouble S = 0, S2 = 0;
+        cimg_for(*this,ptrs,T) { const Tdouble val = (Tdouble)*ptrs; S+=val; S2+=val*val; }
+        variance = (S2 - S*S/siz)/siz;
+        average = S;
+      } break;
+      case 1 : { // Least mean square (robust definition)
+        Tdouble S = 0, S2 = 0;
+        cimg_for(*this,ptrs,T) { const Tdouble val = (Tdouble)*ptrs; S+=val; S2+=val*val; }
+        variance = siz>1?(S2 - S*S/siz)/(siz - 1):0;
+        average = S;
+      } break;
+      case 2 : { // Least Median of Squares (MAD)
+        CImg<Tfloat> buf(*this);
+        buf.sort();
+        const unsigned int siz2 = siz>>1;
+        const Tdouble med_i = (double)buf[siz2];
+        cimg_for(buf,ptrs,Tfloat) { const Tdouble val = (Tdouble)*ptrs; *ptrs = (Tfloat)cimg::abs(val - med_i); average+=val; }
+        buf.sort();
+        const Tdouble sig = (Tdouble)(1.4828*buf[siz2]);
+        variance = sig*sig;
+      } break;
+      default : { // Least trimmed of Squares
+        CImg<Tfloat> buf(*this);
+        const unsigned int siz2 = siz>>1;
+        cimg_for(buf,ptrs,Tfloat) { const Tdouble val = (Tdouble)*ptrs; (*ptrs)=(Tfloat)((*ptrs)*val); average+=val; }
+        buf.sort();
+        Tdouble a = 0;
+        const Tfloat *ptrs = buf._data;
+        for (unsigned int j = 0; j<siz2; ++j) a+=(Tdouble)*(ptrs++);
+        const Tdouble sig = (Tdouble)(2.6477*std::sqrt(a/siz2));
+        variance = sig*sig;
+      }
+      }
+      mean = (t)(average/siz);
+      return variance>0?variance:0;
+    }
+
+    //! Estimate noise variance of the image instance.
+    /**
+       \param variance_method : method to compute the variance
+       \note Because of structures such as edges in images it is
+       recommanded to use a robust variance estimation. The variance of the
+       noise is estimated by computing the variance of the Laplacian \f$(\Delta
+       I)^2 \f$ scaled by a factor \f$c\f$ insuring \f$ c E[(\Delta I)^2]=
+       \sigma^2\f$ where \f$\sigma\f$ is the noise variance.
+       \see variance()
+    **/
+    Tdouble variance_noise(const unsigned int variance_method=2) const {
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "variance_noise() : Empty instance.",
+                                    cimg_instance);
+
+      const unsigned int siz = size();
+      if (!siz || !_data) return 0;
+      if (variance_method>1) { // Compute a scaled version of the Laplacian.
+        CImg<Tdouble> tmp(*this);
+        if (_depth==1) {
+          const Tdouble cste = 1.0/std::sqrt(20.0); // Depends on how the Laplacian is computed.
+          CImg_3x3(I,T);
+          cimg_forC(*this,c) cimg_for3x3(*this,x,y,0,c,I,T) {
+            tmp(x,y,c) = cste*((Tdouble)Inc + (Tdouble)Ipc + (Tdouble)Icn +
+                               (Tdouble)Icp - 4*(Tdouble)Icc);
+          }
+        } else {
+          const Tdouble cste = 1.0/std::sqrt(42.0); // Depends on how the Laplacian is computed.
+          CImg_3x3x3(I,T);
+          cimg_forC(*this,c) cimg_for3x3x3(*this,x,y,z,c,I,T) {
+            tmp(x,y,z,c) = cste*(
+                                 (Tdouble)Incc + (Tdouble)Ipcc + (Tdouble)Icnc + (Tdouble)Icpc +
+                                 (Tdouble)Iccn + (Tdouble)Iccp - 6*(Tdouble)Iccc);
+          }
+        }
+        return tmp.variance(variance_method);
+      }
+
+      // Version that doesn't need intermediate images.
+      Tdouble variance = 0, S = 0, S2 = 0;
+      if (_depth==1) {
+        const Tdouble cste = 1.0/std::sqrt(20.0);
+        CImg_3x3(I,T);
+        cimg_forC(*this,c) cimg_for3x3(*this,x,y,0,c,I,T) {
+          const Tdouble val = cste*((Tdouble)Inc + (Tdouble)Ipc +
+                                    (Tdouble)Icn + (Tdouble)Icp - 4*(Tdouble)Icc);
+          S+=val; S2+=val*val;
+        }
+      } else {
+        const Tdouble cste = 1.0/std::sqrt(42.0);
+        CImg_3x3x3(I,T);
+        cimg_forC(*this,c) cimg_for3x3x3(*this,x,y,z,c,I,T) {
+          const Tdouble val = cste *
+            ((Tdouble)Incc + (Tdouble)Ipcc + (Tdouble)Icnc +
+             (Tdouble)Icpc +
+             (Tdouble)Iccn + (Tdouble)Iccp - 6*(Tdouble)Iccc);
+          S+=val; S2+=val*val;
+        }
+      }
+      if (variance_method) variance = siz>1?(S2 - S*S/siz)/(siz - 1):0;
+      else variance = (S2 - S*S/siz)/siz;
+      return variance>0?variance:0;
+    }
+
+    //! Compute the MSE (Mean-Squared Error) between two images.
+    template<typename t>
+    Tdouble MSE(const CImg<t>& img) const {
+      if (img.size()!=size())
+        throw CImgArgumentException(_cimg_instance
+                                    "MSE() : Instance and specified image (%u,%u,%u,%u,%p) have different dimensions.",
+                                    cimg_instance,
+                                    img._width,img._height,img._depth,img._spectrum,img._data);
+      Tdouble vMSE = 0;
+      const t* ptr2 = img.end();
+      cimg_for(*this,ptr1,T) {
+        const Tdouble diff = (Tdouble)*ptr1 - (Tdouble)*(--ptr2);
+        vMSE+=diff*diff;
+      }
+      const unsigned int siz = img.size();
+      if (siz) vMSE/=siz;
+      return vMSE;
+    }
+
+    //! Compute the PSNR between two images.
+    template<typename t>
+    Tdouble PSNR(const CImg<t>& img, const Tdouble valmax=255) const {
+      const Tdouble vMSE = (Tdouble)std::sqrt(MSE(img));
+      return (vMSE!=0)?(Tdouble)(20*std::log10(valmax/vMSE)):(Tdouble)(cimg::type<Tdouble>::max());
+    }
+
+    //! Evaluate math expression.
+    /**
+       If you make successive evaluations on the same image and with the same expression,
+       you can set 'expr' to 0 after the first call, to skip the math parsing step.
+    **/
+    double eval(const char *const expression, const double x=0, const double y=0, const double z=0, const double c=0) const {
+      static _cimg_math_parser *mp = 0;
+      if (expression) { delete mp; mp = 0; mp = new _cimg_math_parser(*this,expression,"eval"); }
+      return mp?mp->eval(x,y,z,c):0;
+    }
+
+    //! Compute a statistics vector (min,max,mean,variance,xmin,ymin,zmin,cmin,xmax,ymax,zmax,cmax).
+    CImg<T>& stats(const unsigned int variance_method=1) {
+      return get_stats(variance_method).move_to(*this);
+    }
+
+    CImg<Tdouble> get_stats(const unsigned int variance_method=1) const {
+      if (is_empty()) return CImg<doubleT>();
+      const unsigned int siz = size();
+      const T *const odata = _data;
+      const T *pm = odata, *pM = odata;
+      Tdouble S = 0, S2 = 0;
+      T m = *pm, M = m;
+      cimg_for(*this,ptrs,T) {
+        const T val = *ptrs;
+        const Tdouble _val = (Tdouble)val;
+        if (val<m) { m = val; pm = ptrs; }
+        if (val>M) { M = val; pM = ptrs; }
+        S+=_val;
+        S2+=_val*_val;
+      }
+      const Tdouble
+        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, cm = 0,
+        xM = 0, yM = 0, zM = 0, cM = 0;
+      contains(*pm,xm,ym,zm,cm);
+      contains(*pM,xM,yM,zM,cM);
+      return CImg<Tdouble>(1,12).fill((Tdouble)m,(Tdouble)M,mean_value,variance_value,
+                                      (Tdouble)xm,(Tdouble)ym,(Tdouble)zm,(Tdouble)cm,
+                                      (Tdouble)xM,(Tdouble)yM,(Tdouble)zM,(Tdouble)cM);
+    }
+
+    //@}
+    //-------------------------------------
+    //
+    //! \name Vector / Matrix Operations
+    //@{
+    //-------------------------------------
+
+    //! Return the norm of the current vector/matrix. \p ntype = norm type (0=L2, 1=L1, -1=Linf).
+    Tdouble magnitude(const int magnitude_type=2) const {
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "magnitude() : Empty instance.",
+                                    cimg_instance);
+      Tdouble res = 0;
+      switch (magnitude_type) {
+      case -1 : {
+        cimg_for(*this,ptrs,T) { const Tdouble val = (Tdouble)cimg::abs(*ptrs); if (val>res) res = val; }
+      } break;
+      case 1 : {
+        cimg_for(*this,ptrs,T) res+=(Tdouble)cimg::abs(*ptrs);
+      } break;
+      default : {
+        cimg_for(*this,ptrs,T) res+=(Tdouble)cimg::sqr(*ptrs);
+        res = (Tdouble)std::sqrt(res);
+      }
+      }
+      return res;
+    }
+
+    //! Return the trace of the image, viewed as a matrix.
+    Tdouble trace() const {
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "trace() : Empty instance.",
+                                    cimg_instance);
+      Tdouble res = 0;
+      cimg_forX(*this,k) res+=(Tdouble)(*this)(k,k);
+      return res;
+    }
+
+    //! Return the determinant of the image, viewed as a matrix.
+    Tdouble det() const {
+      if (is_empty() || _width!=_height || _depth!=1 || _spectrum!=1)
+        throw CImgInstanceException(_cimg_instance
+                                    "det() : Instance is not a square matrix.",
+                                    cimg_instance);
+
+      switch (_width) {
+      case 1 : return (Tdouble)((*this)(0,0));
+      case 2 : return (Tdouble)((*this)(0,0))*(Tdouble)((*this)(1,1)) - (Tdouble)((*this)(0,1))*(Tdouble)((*this)(1,0));
+      case 3 : {
+        const Tdouble
+          a = (Tdouble)_data[0], d = (Tdouble)_data[1], g = (Tdouble)_data[2],
+          b = (Tdouble)_data[3], e = (Tdouble)_data[4], h = (Tdouble)_data[5],
+          c = (Tdouble)_data[6], f = (Tdouble)_data[7], i = (Tdouble)_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);
+        Tdouble res = d?(Tdouble)1:(Tdouble)-1;
+        cimg_forX(lu,i) res*=lu(i,i);
+        return res;
+      }
+      }
+      return 0;
+    }
+
+    //! Return the dot product of the current vector/matrix with the vector/matrix \p img.
+    template<typename t>
+    Tdouble dot(const CImg<t>& img) const {
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "dot() : Empty instance.",
+                                    cimg_instance);
+      if (!img)
+        throw CImgArgumentException(_cimg_instance
+                                    "dot() : Empty specified image.",
+                                    cimg_instance);
+
+      const unsigned int nb = cimg::min(size(),img.size());
+      Tdouble res = 0;
+      for (unsigned int off = 0; off<nb; ++off) res+=(Tdouble)_data[off]*(Tdouble)img[off];
+      return res;
+    }
+
+    //! 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 {
+      _cimg_static CImg<T> res;
+      if (res._height!=_spectrum) res.assign(1,_spectrum);
+      const unsigned int whd = _width*_height*_depth;
+      const T *ptrs = data(x,y,z);
+      T *ptrd = res._data;
+      cimg_forC(*this,c) { *(ptrd++) = *ptrs; ptrs+=whd; }
+      return res;
+    }
+
+    //! 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)std::sqrt((double)_spectrum);
+      const T *ptrs = data(x,y,z,0);
+      const unsigned int whd = _width*_height*_depth;
+      CImg<T> res(n,n);
+      T *ptrd = res._data;
+      cimg_forC(*this,c) { *(ptrd++) = *ptrs; ptrs+=whd; }
+      return res;
+    }
+
+    //! 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 {
+      const T *ptrs = data(x,y,z,0);
+      const unsigned int whd = _width*_height*_depth;
+      if (_spectrum==6) return tensor(*ptrs,*(ptrs+whd),*(ptrs+2*whd),*(ptrs+3*whd),*(ptrs+4*whd),*(ptrs+5*whd));
+      if (_spectrum==3) return tensor(*ptrs,*(ptrs+whd),*(ptrs+2*whd));
+      return tensor(*ptrs);
+    }
+
+    //! 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 t *ptrs = vec._data;
+        const unsigned int whd = _width*_height*_depth;
+        T *ptrd = data(x,y,z);
+        for (unsigned int k = cimg::min((unsigned int)vec.size(),_spectrum); k; --k) { *ptrd = (T)*(ptrs++); ptrd+=whd; }
+      }
+      return *this;
+    }
+
+    //! 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);
+    }
+
+    //! 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) {
+      T *ptrd = data(x,y,z,0);
+      const unsigned int siz = _width*_height*_depth;
+      if (ten._height==2) {
+        *ptrd = (T)ten[0]; ptrd+=siz;
+        *ptrd = (T)ten[1]; ptrd+=siz;
+        *ptrd = (T)ten[3];
+      }
+      else {
+        *ptrd = (T)ten[0]; ptrd+=siz;
+        *ptrd = (T)ten[1]; ptrd+=siz;
+        *ptrd = (T)ten[2]; ptrd+=siz;
+        *ptrd = (T)ten[4]; ptrd+=siz;
+        *ptrd = (T)ten[5]; ptrd+=siz;
+        *ptrd = (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 image instance 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_instance
+                                         "matrix() : Invalid instance size %u (should be a square integer).",
+                                         cimg_instance,
+                                         siz);
+      }
+      }
+      return *this;
+    }
+
+    CImg<T> get_matrix() const {
+      return (+*this).matrix();
+    }
+
+    //! Realign pixel values of the image instance as a symmetric tensor.
+    CImg<T>& tensor() {
+      return get_tensor().move_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_instance
+                                    "tensor() : Invalid instance size (does not define a 1x1, 2x2 or 3x3 tensor).",
+                                    cimg_instance);
+      }
+      return res;
+    }
+
+    //! Return a diagonal matrix, whose diagonal coefficients are the coefficients of the input image.
+    CImg<T>& diagonal() {
+      return get_diagonal().move_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;
+    }
+
+    //! Return an identity matrix having same dimension than image instance.
+    CImg<T>& identity_matrix() {
+      return identity_matrix(cimg::max(_width,_height)).move_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 Tdouble delta = (Tdouble)a1 - (Tdouble)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_forYZC(*this,y,z,c) for (int x = y; x<width(); ++x) cimg::swap((*this)(x,y,z,c),(*this)(y,x,z,c));
+        return *this;
+      }
+      return get_transpose().move_to(*this);
+    }
+
+    CImg<T> get_transpose() const {
+      return get_permute_axes("yxzc");
+    }
+
+    //! 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_instance
+                                    "cross() : Instance and/or specified image (%u,%u,%u,%u,%p) are not 3d vectors.",
+                                    cimg_instance,
+                                    img._width,img._height,img._depth,img._spectrum,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<_cimg_Tt> get_cross(const CImg<t>& img) const {
+      return CImg<_cimg_Tt>(*this).cross(img);
+    }
+
+    //! Invert the current matrix.
+    CImg<T>& invert(const bool use_LU=true) {
+      if (_width!=_height || _depth!=1 || _spectrum!=1)
+        throw CImgInstanceException(_cimg_instance
+                                    "invert() : Instance is not a square matrix.",
+                                    cimg_instance);
+#ifdef cimg_use_lapack
+      int INFO = (int)use_LU, N = _width, LWORK = 4*N, *const IPIV = new int[N];
+      Tfloat
+        *const lapA = new Tfloat[N*N],
+        *const 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_instance
+                   "invert() : LAPACK function dgetrf_() returned error code %d.",
+                   cimg_instance,
+                   INFO);
+      else {
+        cimg::getri(N,lapA,IPIV,WORK,LWORK,INFO);
+        if (INFO)
+          cimg::warn(_cimg_instance
+                     "invert() : LAPACK function dgetri_() returned error code %d.",
+                     cimg_instance,
+                     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().move_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();
+    }
+
+    //! Solve a linear system AX=B where B=*this.
+    template<typename t>
+    CImg<T>& solve(const CImg<t>& A) {
+      if (_width!=1 || _depth!=1 || _spectrum!=1 || _height!=A._height || A._depth!=1 || A._spectrum!=1)
+        throw CImgArgumentException(_cimg_instance
+                                    "solve() : Instance and specified matrix (%u,%u,%u,%u,%p) have incompatible dimensions.",
+                                    cimg_instance,
+                                    A._width,A._height,A._depth,A._spectrum,A._data);
+      typedef _cimg_Ttfloat Ttfloat;
+      if (A._width==A._height) {
+#ifdef cimg_use_lapack
+        char TRANS = 'N';
+        int INFO, N = _height, LWORK = 4*N, one = 1, *const IPIV = new int[N];
+        Ttfloat
+          *const lapA = new Ttfloat[N*N],
+          *const lapB = new Ttfloat[N],
+          *const WORK = new Ttfloat[LWORK];
+        cimg_forXY(A,k,l) lapA[k*N+l] = (Ttfloat)(A(k,l));
+        cimg_forY(*this,i) lapB[i] = (Ttfloat)((*this)(i));
+        cimg::getrf(N,lapA,IPIV,INFO);
+        if (INFO)
+          cimg::warn(_cimg_instance
+                     "solve() : LAPACK library function dgetrf_() returned error code %d.",
+                     cimg_instance,
+                     INFO);
+
+        if (!INFO) {
+          cimg::getrs(TRANS,N,lapA,IPIV,lapB,INFO);
+          if (INFO)
+            cimg::warn(_cimg_instance
+                       "solve() : LAPACK library function dgetrs_() returned error code %d.",
+                       cimg_instance,
+                       INFO);
+        }
+        if (!INFO) cimg_forY(*this,i) (*this)(i) = (T)(lapB[i]); else fill(0);
+        delete[] IPIV; delete[] lapA; delete[] lapB; delete[] WORK;
+#else
+        CImg<Ttfloat> lu(A,false);
+        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<_cimg_Ttfloat> get_solve(const CImg<t>& A) const {
+      return CImg<_cimg_Ttfloat>(*this,false).solve(A);
+    }
+
+    template<typename t, typename ti>
+    CImg<T>& _solve(const CImg<t>& A, const CImg<ti>& indx) {
+      typedef _cimg_Ttfloat 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 ],
+    // stored as a 3 columns matrix (resolution uses the Thomas Algorithm).
+    template<typename t>
+    CImg<T>& solve_tridiagonal(const CImg<t>& A) {
+      const unsigned int siz = (int)size();
+      if (A._width!=3 || A._height!=siz)
+        throw CImgArgumentException(_cimg_instance
+                                    "solve_tridiagonal() : Instance and tridiagonal matrix "
+                                    "(%u,%u,%u,%u,%p) have incompatible dimensions.",
+                                    cimg_instance,
+                                    A._width,A._height,A._depth,A._spectrum,A._data);
+      typedef _cimg_Ttfloat Ttfloat;
+      const Ttfloat epsilon = 1e-4;
+      CImg<Ttfloat> B = A.get_column(1), V(*this,false);
+      for (int i = 1; i<(int)siz; ++i) {
+        const Ttfloat m = A(0,i)/(B[i-1]?B[i-1]:epsilon);
+        B[i] -= m*A(2,i-1);
+        V[i] -= m*V[i-1];
+      }
+      (*this)[siz-1] = (T)(V[siz-1]/(B[siz-1]?B[siz-1]:epsilon));
+      for (int i = (int)siz - 2; i>=0; --i) (*this)[i] = (T)((V[i] - A(2,i)*(*this)[i+1])/(B[i]?B[i]:epsilon));
+      return *this;
+    }
+
+    template<typename t>
+    CImg<_cimg_Ttfloat> get_solve_tridiagonal(const CImg<t>& A) const {
+      return CImg<_cimg_Ttfloat>(*this,false).solve_tridiagonal(A);
+    }
+
+    //! 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 || _spectrum>1)
+          throw CImgInstanceException(_cimg_instance
+                                      "eigen() : Instance is not a square matrix.",
+                                      cimg_instance);
+
+        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_instance
+                       "CImg<%s>::eigen() : Complex eigenvalues found.",
+                       cimg_instance);
+
+          f = std::sqrt(f);
+          const double l1 = 0.5*(e-f), l2 = 0.5*(e+f);
+          const double theta1 = std::atan2(l2-a,b), theta2 = std::atan2(l1-a,b);
+          val[0] = (t)l2;
+          val[1] = (t)l1;
+          vec(0,0) = (t)std::cos(theta1);
+          vec(0,1) = (t)std::sin(theta1);
+          vec(1,0) = (t)std::cos(theta2);
+          vec(1,1) = (t)std::sin(theta2);
+        } break;
+        default :
+          throw CImgInstanceException(_cimg_instance
+                                      "eigen() : Eigenvalues computation of general matrices is limited to 2x2 matrices.",
+                                      cimg_instance);
+        }
+      }
+      return *this;
+    }
+
+    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
+          *const lapA = new Tfloat[N*N],
+          *const lapW = new Tfloat[N],
+          *const 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_instance
+                     "symmetric_eigen() : LAPACK library function dsyev_() returned error code %d.",
+                     cimg_instance,
+                     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 || _spectrum>1)
+          throw CImgInstanceException(_cimg_instance
+                                      "eigen() : Instance is not a square matrix.",
+                                      cimg_instance);
+
+        val.assign(1,_width);
+        if (vec._data) vec.assign(_width,_width);
+        if (_width<3) {
+          eigen(val,vec);
+          if (_width==2) { vec[1] = -vec[2]; vec[3] = vec[0]; } // Force orthogonality for 2x2 matrices.
+          return *this;
+        }
+        CImg<t> V(_width,_width);
+        SVD(vec,val,V,false);
+        bool is_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) is_ambiguous = true;
+          if (scal<0) val[p] = -val[p];
+        }
+        if (is_ambiguous) {
+          ++(eig*=2);
+          SVD(vec,val,V,false,40,eig);
+          val-=eig;
+        }
+        CImg<intT> permutations;  // sort eigenvalues in decreasing order
+        CImg<t> tmp(_width);
+        val.sort(permutations,false);
+        cimg_forY(vec,k) {
+          cimg_forY(permutations,y) tmp(y) = vec(permutations(y),k);
+          std::memcpy(vec.data(0,k),tmp._data,sizeof(t)*_width);
+        }
+#endif
+      }
+      return *this;
+    }
+
+    CImgList<Tfloat> get_symmetric_eigen() const {
+      CImgList<Tfloat> res(2);
+      symmetric_eigen(res[0],res[1]);
+      return res;
+    }
+
+    //! Sort values of a vector and get corresponding permutations.
+    template<typename t>
+    CImg<T>& sort(CImg<t>& permutations, const bool increasing=true) {
+      permutations.assign(_width,_height,_depth,_spectrum);
+      if (is_empty()) return *this;
+      cimg_foroff(permutations,off) permutations[off] = (t)off;
+      return _quicksort(0,size()-1,permutations,increasing,true);
+    }
+
+    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, const char axis=0) {
+      if (is_empty()) return *this;
+      CImg<uintT> perm;
+      switch (axis) {
+      case 0 :
+        _quicksort(0,size()-1,perm,increasing,false);
+        break;
+      case 'x' : {
+        perm.assign(_width);
+        get_crop(0,0,0,0,_width-1,0,0,0).sort(perm,increasing);
+        CImg<T> img(*this,false);
+        cimg_forXYZC(*this,x,y,z,c) (*this)(x,y,z,c) = img(perm[x],y,z,c);
+      } break;
+      case 'y' : {
+        perm.assign(_height);
+        get_crop(0,0,0,0,0,_height-1,0,0).sort(perm,increasing);
+        CImg<T> img(*this,false);
+        cimg_forXYZC(*this,x,y,z,c) (*this)(x,y,z,c) = img(x,perm[y],z,c);
+      } break;
+      case 'z' : {
+        perm.assign(_depth);
+        get_crop(0,0,0,0,0,0,_depth-1,0).sort(perm,increasing);
+        CImg<T> img(*this,false);
+        cimg_forXYZC(*this,x,y,z,c) (*this)(x,y,z,c) = img(x,y,perm[z],c);
+      } break;
+      case 'c' : {
+        perm.assign(_spectrum);
+        get_crop(0,0,0,0,0,0,0,_spectrum-1).sort(perm,increasing);
+        CImg<T> img(*this,false);
+        cimg_forXYZC(*this,x,y,z,c) (*this)(x,y,z,c) = img(x,y,z,perm[c]);
+      } break;
+      default :
+        throw CImgArgumentException(_cimg_instance
+                                    "sort() : Invalid specified axis '%c' "
+                                    "(should be { x | y | z | c }).",
+                                    cimg_instance,axis);
+      }
+      return *this;
+    }
+
+    CImg<T> get_sort(const bool increasing=true, const char axis=0) const {
+      return (+*this).sort(increasing,axis);
+    }
+
+    template<typename t>
+    CImg<T>& _quicksort(const int indm, const int indM, CImg<t>& permutations, const bool increasing, const bool is_permutations) {
+      if (indm<indM) {
+        const int mid = (indm + indM)/2;
+        if (increasing) {
+          if ((*this)[indm]>(*this)[mid]) {
+            cimg::swap((*this)[indm],(*this)[mid]); if (is_permutations) cimg::swap(permutations[indm],permutations[mid]);
+          }
+          if ((*this)[mid]>(*this)[indM]) {
+            cimg::swap((*this)[indM],(*this)[mid]); if (is_permutations) cimg::swap(permutations[indM],permutations[mid]);
+          }
+          if ((*this)[indm]>(*this)[mid]) {
+            cimg::swap((*this)[indm],(*this)[mid]); if (is_permutations) cimg::swap(permutations[indm],permutations[mid]);
+          }
+        } else {
+          if ((*this)[indm]<(*this)[mid]) {
+            cimg::swap((*this)[indm],(*this)[mid]); if (is_permutations) cimg::swap(permutations[indm],permutations[mid]);
+          }
+          if ((*this)[mid]<(*this)[indM]) {
+            cimg::swap((*this)[indM],(*this)[mid]); if (is_permutations) cimg::swap(permutations[indM],permutations[mid]);
+          }
+          if ((*this)[indm]<(*this)[mid]) {
+            cimg::swap((*this)[indm],(*this)[mid]); if (is_permutations) cimg::swap(permutations[indm],permutations[mid]);
+          }
+        }
+        if (indM - indm>=3) {
+          const T pivot = (*this)[mid];
+          int i = indm, j = indM;
+          if (increasing) {
+            do {
+              while ((*this)[i]<pivot) ++i;
+              while ((*this)[j]>pivot) --j;
+              if (i<=j) {
+                if (is_permutations) cimg::swap(permutations[i],permutations[j]);
+                cimg::swap((*this)[i++],(*this)[j--]);
+              }
+            } while (i<=j);
+          } else {
+            do {
+              while ((*this)[i]>pivot) ++i;
+              while ((*this)[j]<pivot) --j;
+              if (i<=j) {
+                if (is_permutations) cimg::swap(permutations[i],permutations[j]);
+                cimg::swap((*this)[i++],(*this)[j--]);
+              }
+            } while (i<=j);
+          }
+          if (indm<j) _quicksort(indm,j,permutations,increasing,is_permutations);
+          if (i<indM) _quicksort(i,indM,permutations,increasing,is_permutations);
+        }
+      }
+      return *this;
+    }
+
+    //! 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_iteration=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<height()) {
+            for (int k = i; k<height(); ++k) scale+= cimg::abs(U(i,k));
+            if (scale) {
+              for (int k = i; k<height(); ++k) { U(i,k)/=scale; s+= U(i,k)*U(i,k); }
+              f = U(i,i); g = (t)((f>=0?-1:1)*std::sqrt(s)); h=f*g-s; U(i,i) = f-g;
+              for (int j = l; j<width(); ++j) {
+                s = 0;
+                for (int k=i; k<height(); ++k) s+= U(i,k)*U(j,k);
+                f = s/h;
+                for (int k = i; k<height(); ++k) U(j,k)+= f*U(i,k);
+              }
+              for (int k = i; k<height(); ++k) U(i,k)*= scale;
+            }
+          }
+          S[i]=scale*g;
+
+          g = s = scale = 0;
+          if (i<height() && i!=width()-1) {
+            for (int k = l; k<width(); ++k) scale+=cimg::abs(U(k,i));
+            if (scale) {
+              for (int k = l; k<width(); ++k) { U(k,i)/= scale; s+= U(k,i)*U(k,i); }
+              f = U(l,i); g = (t)((f>=0?-1:1)*std::sqrt(s)); h = f*g-s; U(l,i) = f-g;
+              for (int k = l; k<width(); ++k) rv1[k]=U(k,i)/h;
+              for (int j = l; j<height(); ++j) {
+                s = 0;
+                for (int k = l; k<width(); ++k) s+= U(k,j)*U(k,i);
+                for (int k = l; k<width(); ++k) U(k,j)+= s*rv1[k];
+              }
+              for (int k = l; k<width(); ++k) U(k,i)*= scale;
+            }
+          }
+          anorm = (t)cimg::max((float)anorm,(float)(cimg::abs(S[i])+cimg::abs(rv1[i])));
+        }
+
+        for (int i = width()-1; i>=0; --i) {
+          if (i<width()-1) {
+            if (g) {
+              for (int j = l; j<width(); ++j) V(i,j) =(U(j,i)/U(l,i))/g;
+              for (int j = l; j<width(); ++j) {
+                s = 0;
+                for (int k = l; k<width(); ++k) s+= U(k,i)*V(j,k);
+                for (int k = l; k<width(); ++k) V(j,k)+= s*V(i,k);
+              }
+            }
+            for (int j = l; j<width(); ++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(width(),height())-1; i>=0; --i) {
+          l = i+1; g = S[i];
+          for (int j = l; j<width(); ++j) U(j,i) = 0;
+          if (g) {
+            g = 1/g;
+            for (int j = l; j<width(); ++j) {
+              s = 0; for (int k = l; k<height(); ++k) s+= U(i,k)*U(j,k);
+              f = (s/U(i,i))*g;
+              for (int k = i; k<height(); ++k) U(j,k)+= f*U(i,k);
+            }
+            for (int j = i; j<height(); ++j) U(i,j)*= g;
+          } else for (int j = i; j<height(); ++j) U(i,j) = 0;
+          ++U(i,i);
+        }
+
+        for (int k = width()-1; k>=0; --k) {
+          for (unsigned int its = 0; its<max_iteration; ++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;
+          CImg<t> tmp(_width);
+          S.sort(permutations,false);
+          cimg_forY(U,k) {
+            cimg_forY(permutations,y) tmp(y) = U(permutations(y),k);
+            std::memcpy(U.data(0,k),tmp._data,sizeof(t)*_width);
+          }
+          cimg_forY(V,k) {
+            cimg_forY(permutations,y) tmp(y) = V(permutations(y),k);
+            std::memcpy(V.data(0,k),tmp._data,sizeof(t)*_width);
+          }
+        }
+      }
+      return *this;
+    }
+
+    CImgList<Tfloat> get_SVD(const bool sorting=true,
+                             const unsigned int max_iteration=40, const float lambda=0) const {
+      CImgList<Tfloat> res(3);
+      SVD(res[0],res[1],res[2],sorting,max_iteration,lambda);
+      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 = width();
+      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<width(); ++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 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) {
+      if (starting_node>=nb_nodes)
+        throw CImgArgumentException("CImg<%s>::dijkstra() : Specified indice of starting node %u is higher than number of nodes %u.",
+                                    pixel_type(),starting_node,nb_nodes);
+      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.
+    /**
+       image instance 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).move_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 || _spectrum!=1)
+        throw CImgInstanceException(_cimg_instance
+                                    "dijkstra() : Instance is not a graph adjacency matrix.",
+                                    cimg_instance);
+
+      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).move_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);
+    }
+
+    //! Return stream line of a 2d or 3d vector field.
+    CImg<floatT> get_streamline(const float x, const float y, const float z,
+                                const float L=256, const float dl=0.1f,
+                                const unsigned int interpolation_type=2, const bool is_backward_tracking=false,
+                                const bool is_oriented_only=false) const {
+      if (_spectrum!=2 && _spectrum!=3)
+        throw CImgInstanceException(_cimg_instance
+                                    "streamline() : Instance is not a 2d or 3d vector field.",
+                                    cimg_instance);
+      if (_spectrum==2) {
+        if (is_oriented_only) {
+          typename CImg<T>::_functor4d_streamline2d_oriented func(*this);
+          return streamline(func,x,y,z,L,dl,interpolation_type,is_backward_tracking,true,0,0,0,_width-1.0f,_height-1.0f,0.0f);
+        } else {
+          typename CImg<T>::_functor4d_streamline2d_directed func(*this);
+          return streamline(func,x,y,z,L,dl,interpolation_type,is_backward_tracking,false,0,0,0,_width-1.0f,_height-1.0f,0.0f);
+        }
+      }
+      if (is_oriented_only) {
+        typename CImg<T>::_functor4d_streamline3d_oriented func(*this);
+        return streamline(func,x,y,z,L,dl,interpolation_type,is_backward_tracking,true,0,0,0,_width-1.0f,_height-1.0f,_depth-1.0f);
+      }
+      typename CImg<T>::_functor4d_streamline3d_directed func(*this);
+      return streamline(func,x,y,z,L,dl,interpolation_type,is_backward_tracking,false,0,0,0,_width-1.0f,_height-1.0f,_depth-1.0f);
+    }
+
+    //! Return stream line of a 3d vector field.
+    /**
+       \param interpolation_type Type of interpolation (can be 0=nearest int, 1=linear, 2=2nd-order RK, 3=4th-order RK.
+
+     **/
+    template<typename tfunc>
+    static CImg<floatT> streamline(const tfunc& func,
+                                   const float x, const float y, const float z,
+                                   const float L=256, const float dl=0.1f,
+                                   const unsigned int interpolation_type=2, const bool is_backward_tracking=false,
+                                   const bool is_oriented_only=false,
+                                   const float x0=0, const float y0=0, const float z0=0,
+                                   const float x1=0, const float y1=0, const float z1=0) {
+      if (dl<=0)
+        throw CImgArgumentException("CImg<%s>::streamline() : Invalid specified integration length %g "
+                                    "(should be >0).",
+                                    pixel_type(),
+                                    dl);
+
+      const bool is_bounded = (x0!=x1 || y0!=y1 || z0!=z1);
+      if (L<=0 || (is_bounded && (x<x0 || x>x1 || y<y0 || y>y1 || z<z0 || z>z1))) return CImg<floatT>();
+      const unsigned int size_L = (unsigned int)cimg::round(L/dl+1);
+      CImg<floatT> coordinates(size_L,3);
+      const float dl2 = dl/2;
+      float
+        *ptr_x = coordinates.data(0,0),
+        *ptr_y = coordinates.data(0,1),
+        *ptr_z = coordinates.data(0,2),
+        pu = (float)(dl*func(x,y,z,0)),
+        pv = (float)(dl*func(x,y,z,1)),
+        pw = (float)(dl*func(x,y,z,2)),
+        X = x, Y = y, Z = z;
+
+      switch (interpolation_type) {
+      case 0 : { // Nearest integer interpolation.
+        cimg_forX(coordinates,l) {
+          *(ptr_x++) = X; *(ptr_y++) = Y; *(ptr_z++) = Z;
+          const int
+            xi = (int)(X>0?X+0.5f:X-0.5f),
+            yi = (int)(Y>0?Y+0.5f:Y-0.5f),
+            zi = (int)(Z>0?Z+0.5f:Z-0.5f);
+          float
+            u = (float)(dl*func((float)xi,(float)yi,(float)zi,0)),
+            v = (float)(dl*func((float)xi,(float)yi,(float)zi,1)),
+            w = (float)(dl*func((float)xi,(float)yi,(float)zi,2));
+          if (is_oriented_only && u*pu + v*pv + w*pw<0) { u = -u; v = -v; w = -w; }
+          if (is_backward_tracking) { X-=(pu=u); Y-=(pv=v); Z-=(pw=w); } else { X+=(pu=u); Y+=(pv=v); Z+=(pw=w); }
+          if (is_bounded && (X<x0 || X>x1 || Y<y0 || Y>y1 || Z<z0 || Z>z1)) break;
+        }
+      } break;
+      case 1 : { // First-order interpolation.
+        cimg_forX(coordinates,l) {
+          *(ptr_x++) = X; *(ptr_y++) = Y; *(ptr_z++) = Z;
+          float
+            u = (float)(dl*func(X,Y,Z,0)),
+            v = (float)(dl*func(X,Y,Z,1)),
+            w = (float)(dl*func(X,Y,Z,2));
+          if (is_oriented_only && u*pu + v*pv + w*pw<0) { u = -u; v = -v; w = -w; }
+          if (is_backward_tracking) { X-=(pu=u); Y-=(pv=v); Z-=(pw=w); } else { X+=(pu=u); Y+=(pv=v); Z+=(pw=w); }
+          if (is_bounded && (X<x0 || X>x1 || Y<y0 || Y>y1 || Z<z0 || Z>z1)) break;
+        }
+      } break;
+      case 2 : { // Second order interpolation.
+        cimg_forX(coordinates,l) {
+          *(ptr_x++) = X; *(ptr_y++) = Y; *(ptr_z++) = Z;
+          float
+            u0 = (float)(dl2*func(X,Y,Z,0)),
+            v0 = (float)(dl2*func(X,Y,Z,1)),
+            w0 = (float)(dl2*func(X,Y,Z,2));
+          if (is_oriented_only && u0*pu + v0*pv + w0*pw<0) { u0 = -u0; v0 = -v0; w0 = -w0; }
+          float
+            u = (float)(dl*func(X+u0,Y+v0,Z+w0,0)),
+            v = (float)(dl*func(X+u0,Y+v0,Z+w0,1)),
+            w = (float)(dl*func(X+u0,Y+v0,Z+w0,2));
+          if (is_oriented_only && u*pu + v*pv + w*pw<0) { u = -u; v = -v; w = -w; }
+          if (is_backward_tracking) { X-=(pu=u); Y-=(pv=v); Z-=(pw=w); } else { X+=(pu=u); Y+=(pv=v); Z+=(pw=w); }
+          if (is_bounded && (X<x0 || X>x1 || Y<y0 || Y>y1 || Z<z0 || Z>z1)) break;
+        }
+      } break;
+      default : { // Fourth order interpolation.
+        cimg_forX(coordinates,x) {
+          *(ptr_x++) = X; *(ptr_y++) = Y; *(ptr_z++) = Z;
+          float
+            u0 = (float)(dl2*func(X,Y,Z,0)),
+            v0 = (float)(dl2*func(X,Y,Z,1)),
+            w0 = (float)(dl2*func(X,Y,Z,2));
+          if (is_oriented_only && u0*pu + v0*pv + w0*pw<0) { u0 = -u0; v0 = -v0; w0 = -w0; }
+          float
+            u1 = (float)(dl2*func(X+u0,Y+v0,Z+w0,0)),
+            v1 = (float)(dl2*func(X+u0,Y+v0,Z+w0,1)),
+            w1 = (float)(dl2*func(X+u0,Y+v0,Z+w0,2));
+          if (is_oriented_only && u1*pu + v1*pv + w1*pw<0) { u1 = -u1; v1 = -v1; w1 = -w1; }
+          float
+            u2 = (float)(dl2*func(X+u1,Y+v1,Z+w1,0)),
+            v2 = (float)(dl2*func(X+u1,Y+v1,Z+w1,1)),
+            w2 = (float)(dl2*func(X+u1,Y+v1,Z+w1,2));
+          if (is_oriented_only && u2*pu + v2*pv + w2*pw<0) { u2 = -u2; v2 = -v2; w2 = -w2; }
+          float
+            u3 = (float)(dl2*func(X+u2,Y+v2,Z+w2,0)),
+            v3 = (float)(dl2*func(X+u2,Y+v2,Z+w2,1)),
+            w3 = (float)(dl2*func(X+u2,Y+v2,Z+w2,2));
+          if (is_oriented_only && u2*pu + v2*pv + w2*pw<0) { u3 = -u3; v3 = -v3; w3 = -w3; }
+          const float
+            u = (u0 + u3)/3 + (u1 + u2)/1.5f,
+            v = (v0 + v3)/3 + (v1 + v2)/1.5f,
+            w = (w0 + w3)/3 + (w1 + w2)/1.5f;
+          if (is_backward_tracking) { X-=(pu=u); Y-=(pv=v); Z-=(pw=w); } else { X+=(pu=u); Y+=(pv=v); Z+=(pw=w); }
+          if (is_bounded && (X<x0 || X>x1 || Y<y0 || Y>y1 || Z<z0 || Z>z1)) break;
+        }
+      }
+      }
+      if (ptr_x!=coordinates.data(0,1)) coordinates.resize((int)(ptr_x-coordinates.data()),3,1,1,0);
+      return coordinates;
+    }
+
+    //! Return stream line of a vector field.
+    static CImg<floatT> streamline(const char *const expression,
+                                   const float x, const float y, const float z,
+                                   const float L=256, const float dl=0.1f,
+                                   const unsigned int interpolation_type=2, const bool is_backward_tracking=true,
+                                   const bool is_oriented_only=false,
+                                   const float x0=0, const float y0=0, const float z0=0,
+                                   const float x1=0, const float y1=0, const float z1=0) {
+      _functor4d_streamline_expr func(expression);
+      return streamline(func,x,y,z,L,dl,interpolation_type,is_backward_tracking,is_oriented_only,x0,y0,z0,x1,y1,z1);
+    }
+
+    struct _functor4d_streamline2d_directed {
+      const CImg<T>& ref;
+      _functor4d_streamline2d_directed(const CImg<T>& pref):ref(pref) {}
+      float operator()(const float x, const float y, const float z, const unsigned int c) const {
+        return c<2?(float)ref._linear_atXY(x,y,(int)z,c):0;
+      }
+    };
+
+    struct _functor4d_streamline3d_directed {
+      const CImg<T>& ref;
+      _functor4d_streamline3d_directed(const CImg<T>& pref):ref(pref) {}
+      float operator()(const float x, const float y, const float z, const unsigned int c) const {
+        return (float)ref._linear_atXYZ(x,y,z,c);
+      }
+    };
+
+    struct _functor4d_streamline2d_oriented {
+      const CImg<T>& ref;
+      CImg<floatT> *pI;
+      _functor4d_streamline2d_oriented(const CImg<T>& pref):ref(pref),pI(0) { pI = new CImg<floatT>(2,2,1,2); }
+      ~_functor4d_streamline2d_oriented() { delete pI; }
+      float operator()(const float x, const float y, const float z, const unsigned int c) const {
+#define _cimg_vecalign2d(i,j) if (I(i,j,0)*I(0,0,0)+I(i,j,1)*I(0,0,1)<0) { I(i,j,0) = -I(i,j,0); I(i,j,1) = -I(i,j,1); }
+        int
+          xi = (int)x - (x>=0?0:1), nxi = xi + 1,
+          yi = (int)y - (y>=0?0:1), nyi = yi + 1,
+          zi = (int)z;
+        const float
+          dx = x - xi,
+          dy = y - yi;
+        if (c==0) {
+          CImg<floatT>& I = *pI;
+          if (xi<0) xi = 0; if (nxi<0) nxi = 0;
+          if (xi>=ref.width()) xi = ref.width()-1; if (nxi>=ref.width()) nxi = ref.width()-1;
+          if (yi<0) yi = 0; if (nyi<0) nyi = 0;
+          if (yi>=ref.height()) yi = ref.height()-1; if (nyi>=ref.height()) nyi = ref.height()-1;
+          I(0,0,0) = (float)ref(xi,yi,zi,0); I(0,0,1) = (float)ref(xi,yi,zi,1);
+          I(1,0,0) = (float)ref(nxi,yi,zi,0); I(1,0,1) = (float)ref(nxi,yi,zi,1);
+          I(1,1,0) = (float)ref(nxi,nyi,zi,0); I(1,1,1) = (float)ref(nxi,nyi,zi,1);
+          I(0,1,0) = (float)ref(xi,nyi,zi,0); I(0,1,1) = (float)ref(xi,nyi,zi,1);
+          _cimg_vecalign2d(1,0); _cimg_vecalign2d(1,1); _cimg_vecalign2d(0,1);
+        }
+        return c<2?(float)pI->_linear_atXY(dx,dy,0,c):0;
+      }
+    };
+
+    struct _functor4d_streamline3d_oriented {
+      const CImg<T>& ref;
+      CImg<floatT> *pI;
+      _functor4d_streamline3d_oriented(const CImg<T>& pref):ref(pref),pI(0) { pI = new CImg<floatT>(2,2,2,3); }
+      ~_functor4d_streamline3d_oriented() { delete pI; }
+      float operator()(const float x, const float y, const float z, const unsigned int c) const {
+#define _cimg_vecalign3d(i,j,k) if (I(i,j,k,0)*I(0,0,0,0)+I(i,j,k,1)*I(0,0,0,1)+I(i,j,k,2)*I(0,0,0,2)<0) { \
+  I(i,j,k,0) = -I(i,j,k,0); I(i,j,k,1) = -I(i,j,k,1); I(i,j,k,2) = -I(i,j,k,2); }
+        int
+          xi = (int)x - (x>=0?0:1), nxi = xi + 1,
+          yi = (int)y - (y>=0?0:1), nyi = yi + 1,
+          zi = (int)z - (z>=0?0:1), nzi = zi + 1;
+        const float
+          dx = x - xi,
+          dy = y - yi,
+          dz = z - zi;
+        if (c==0) {
+          CImg<floatT>& I = *pI;
+          if (xi<0) xi = 0; if (nxi<0) nxi = 0;
+          if (xi>=ref.width()) xi = ref.width()-1; if (nxi>=ref.width()) nxi = ref.width()-1;
+          if (yi<0) yi = 0; if (nyi<0) nyi = 0;
+          if (yi>=ref.height()) yi = ref.height()-1; if (nyi>=ref.height()) nyi = ref.height()-1;
+          if (zi<0) zi = 0; if (nzi<0) nzi = 0;
+          if (zi>=ref.depth()) zi = ref.depth()-1; if (nzi>=ref.depth()) nzi = ref.depth()-1;
+          I(0,0,0,0) = (float)ref(xi,yi,zi,0); I(0,0,0,1) = (float)ref(xi,yi,zi,1); I(0,0,0,2) = (float)ref(xi,yi,zi,2);
+          I(1,0,0,0) = (float)ref(nxi,yi,zi,0); I(1,0,0,1) = (float)ref(nxi,yi,zi,1); I(1,0,0,2) = (float)ref(nxi,yi,zi,2);
+          I(1,1,0,0) = (float)ref(nxi,nyi,zi,0); I(1,1,0,1) = (float)ref(nxi,nyi,zi,1); I(1,1,0,2) = (float)ref(nxi,nyi,zi,2);
+          I(0,1,0,0) = (float)ref(xi,nyi,zi,0); I(0,1,0,1) = (float)ref(xi,nyi,zi,1); I(0,1,0,2) = (float)ref(xi,yi,zi,2);
+          I(0,0,0,1) = (float)ref(xi,yi,nzi,0); I(0,0,0,1) = (float)ref(xi,yi,nzi,1); I(0,0,0,2) = (float)ref(xi,yi,nzi,2);
+          I(1,0,0,1) = (float)ref(nxi,yi,nzi,0); I(1,0,0,1) = (float)ref(nxi,yi,nzi,1); I(1,0,0,2) = (float)ref(nxi,yi,nzi,2);
+          I(1,1,0,1) = (float)ref(nxi,nyi,nzi,0); I(1,1,0,1) = (float)ref(nxi,nyi,nzi,1); I(1,1,0,2) = (float)ref(nxi,nyi,nzi,2);
+          I(0,1,0,1) = (float)ref(xi,nyi,nzi,0); I(0,1,0,1) = (float)ref(xi,nyi,nzi,1); I(0,1,0,2) = (float)ref(xi,yi,nzi,2);
+          _cimg_vecalign3d(1,0,0); _cimg_vecalign3d(1,1,0); _cimg_vecalign3d(0,1,0);
+          _cimg_vecalign3d(0,0,1); _cimg_vecalign3d(1,0,1); _cimg_vecalign3d(1,1,1); _cimg_vecalign3d(0,1,1);
+        }
+        return (float)pI->_linear_atXYZ(dx,dy,dz,c);
+      }
+    };
+
+    struct _functor4d_streamline_expr {
+      _cimg_math_parser *mp;
+      ~_functor4d_streamline_expr() { delete mp; }
+      _functor4d_streamline_expr(const char *const expr):mp(0) { mp = new _cimg_math_parser(CImg<T>::empty(),expr,"streamline"); }
+      float operator()(const float x, const float y, const float z, const unsigned int c) const {
+        return (float)mp->eval(x,y,z,c);
+      }
+    };
+
+    //! Return an image containing the specified string.
+    static CImg<T> string(const char *const str, const bool include_last_zero=true) {
+      if (!str) return CImg<T>();
+      return CImg<T>(str,std::strlen(str)+(include_last_zero?1:0));
+    }
+
+    //! Return a vector with specified coefficients.
+    static CImg<T> vector(const T& a0) {
+      _cimg_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) {
+      _cimg_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) {
+      _cimg_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) {
+      _cimg_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) {
+      _cimg_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) {
+      _cimg_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) {
+      _cimg_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) {
+      _cimg_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) {
+      _cimg_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) {
+      _cimg_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) {
+      _cimg_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) {
+      _cimg_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) {
+      _cimg_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) {
+      _cimg_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) {
+      _cimg_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) {
+      _cimg_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) {
+      _cimg_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) {
+      _cimg_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) {
+      _cimg_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) {
+      _cimg_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)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)std::sin(nw/2),
+          cosa = (float)std::cos(nw/2);
+        X = nx*sina;
+        Y = ny*sina;
+        Z = nz*sina;
+        W = cosa;
+      } else {
+        const float norm = (float)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)));
+    }
+
+    //@}
+    //-----------------------------------
+    //
+    //! \name Value Manipulation
+    //@{
+    //-----------------------------------
+
+    //! Fill an image by a value \p val.
+    /**
+       \param val = fill value
+       \note All pixel values of the image instance 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,ptrd,T) *ptrd = val;
+      else std::memset(_data,(int)val,size()*sizeof(T));
+      return *this;
+    }
+
+    CImg<T> get_fill(const T val) const {
+      return CImg<T>(_width,_height,_depth,_spectrum).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 *ptrd, *ptre = end()-1;
+      for (ptrd = _data; ptrd<ptre; ) { *(ptrd++) = val0; *(ptrd++) = val1; }
+      if (ptrd!=ptre+1) *(ptrd++) = val0;
+      return *this;
+    }
+
+    CImg<T> get_fill(const T val0, const T val1) const {
+      return CImg<T>(_width,_height,_depth,_spectrum).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 *ptrd, *ptre = end()-2;
+      for (ptrd = _data; ptrd<ptre; ) { *(ptrd++) = val0; *(ptrd++) = val1; *(ptrd++) = val2; }
+      ptre+=2;
+      switch (ptre - ptrd) {
+      case 2 : *(--ptre) = val1;
+      case 1 : *(--ptre) = val0;
+      }
+      return *this;
+    }
+
+    CImg<T> get_fill(const T val0, const T val1, const T val2) const {
+      return CImg<T>(_width,_height,_depth,_spectrum).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 *ptrd, *ptre = end()-3;
+      for (ptrd = _data; ptrd<ptre; ) { *(ptrd++) = val0; *(ptrd++) = val1; *(ptrd++) = val2; *(ptrd++) = val3; }
+      ptre+=3;
+      switch (ptre - ptrd) {
+      case 3 : *(--ptre) = val2;
+      case 2 : *(--ptre) = val1;
+      case 1 : *(--ptre) = 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,_spectrum).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 *ptrd, *ptre = end()-4;
+      for (ptrd = _data; ptrd<ptre; ) { *(ptrd++) = val0; *(ptrd++) = val1; *(ptrd++) = val2; *(ptrd++) = val3; *(ptrd++) = val4; }
+      ptre+=4;
+      switch (ptre - ptrd) {
+      case 4 : *(--ptre) = val3;
+      case 3 : *(--ptre) = val2;
+      case 2 : *(--ptre) = val1;
+      case 1 : *(--ptre) = 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,_spectrum).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 *ptrd, *ptre = end()-5;
+      for (ptrd = _data; ptrd<ptre; ) {
+        *(ptrd++) = val0; *(ptrd++) = val1; *(ptrd++) = val2; *(ptrd++) = val3; *(ptrd++) = val4; *(ptrd++) = val5;
+      }
+      ptre+=5;
+      switch (ptre - ptrd) {
+      case 5 : *(--ptre) = val4;
+      case 4 : *(--ptre) = val3;
+      case 3 : *(--ptre) = val2;
+      case 2 : *(--ptre) = val1;
+      case 1 : *(--ptre) = 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,_spectrum).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 *ptrd, *ptre = end()-6;
+      for (ptrd = _data; ptrd<ptre; ) {
+        *(ptrd++) = val0; *(ptrd++) = val1; *(ptrd++) = val2; *(ptrd++) = val3; *(ptrd++) = val4; *(ptrd++) = val5; *(ptrd++) = val6;
+      }
+      ptre+=6;
+      switch (ptre - ptrd) {
+      case 6 : *(--ptre) = val5;
+      case 5 : *(--ptre) = val4;
+      case 4 : *(--ptre) = val3;
+      case 3 : *(--ptre) = val2;
+      case 2 : *(--ptre) = val1;
+      case 1 : *(--ptre) = 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,_spectrum).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 *ptrd, *ptre = end()-7;
+      for (ptrd = _data; ptrd<ptre; ) {
+        *(ptrd++) = val0; *(ptrd++) = val1; *(ptrd++) = val2; *(ptrd++) = val3;
+        *(ptrd++) = val4; *(ptrd++) = val5; *(ptrd++) = val6; *(ptrd++) = val7;
+      }
+      ptre+=7;
+      switch (ptre - ptrd) {
+      case 7 : *(--ptre) = val6;
+      case 6 : *(--ptre) = val5;
+      case 5 : *(--ptre) = val4;
+      case 4 : *(--ptre) = val3;
+      case 3 : *(--ptre) = val2;
+      case 2 : *(--ptre) = val1;
+      case 1 : *(--ptre) = 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,_spectrum).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 *ptrd, *ptre = end()-8;
+      for (ptrd = _data; ptrd<ptre; ) {
+        *(ptrd++) = val0; *(ptrd++) = val1; *(ptrd++) = val2;
+        *(ptrd++) = val3; *(ptrd++) = val4; *(ptrd++) = val5;
+        *(ptrd++) = val6; *(ptrd++) = val7; *(ptrd++) = val8;
+      }
+      ptre+=8;
+      switch (ptre - ptrd) {
+      case 8 : *(--ptre) = val7;
+      case 7 : *(--ptre) = val6;
+      case 6 : *(--ptre) = val5;
+      case 5 : *(--ptre) = val4;
+      case 4 : *(--ptre) = val3;
+      case 3 : *(--ptre) = val2;
+      case 2 : *(--ptre) = val1;
+      case 1 : *(--ptre) = 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,_spectrum).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 *ptrd, *ptre = end()-9;
+      for (ptrd = _data; ptrd<ptre; ) {
+        *(ptrd++) = val0; *(ptrd++) = val1; *(ptrd++) = val2; *(ptrd++) = val3; *(ptrd++) = val4;
+        *(ptrd++) = val5; *(ptrd++) = val6; *(ptrd++) = val7; *(ptrd++) = val8; *(ptrd++) = val9;
+      }
+      ptre+=9;
+      switch (ptre - ptrd) {
+      case 9 : *(--ptre) = val8;
+      case 8 : *(--ptre) = val7;
+      case 7 : *(--ptre) = val6;
+      case 6 : *(--ptre) = val5;
+      case 5 : *(--ptre) = val4;
+      case 4 : *(--ptre) = val3;
+      case 3 : *(--ptre) = val2;
+      case 2 : *(--ptre) = val1;
+      case 1 : *(--ptre) = 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,_spectrum).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 *ptrd, *ptre = end()-10;
+      for (ptrd = _data; ptrd<ptre; ) {
+        *(ptrd++) = val0; *(ptrd++) = val1; *(ptrd++) = val2; *(ptrd++) = val3; *(ptrd++) = val4;
+        *(ptrd++) = val5; *(ptrd++) = val6; *(ptrd++) = val7; *(ptrd++) = val8; *(ptrd++) = val9;
+        *(ptrd++) = val10;
+      }
+      ptre+=10;
+      switch (ptre - ptrd) {
+      case 10 : *(--ptre) = val9;
+      case 9 : *(--ptre) = val8;
+      case 8 : *(--ptre) = val7;
+      case 7 : *(--ptre) = val6;
+      case 6 : *(--ptre) = val5;
+      case 5 : *(--ptre) = val4;
+      case 4 : *(--ptre) = val3;
+      case 3 : *(--ptre) = val2;
+      case 2 : *(--ptre) = val1;
+      case 1 : *(--ptre) = 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,_spectrum).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 *ptrd, *ptre = end()-11;
+      for (ptrd = _data; ptrd<ptre; ) {
+        *(ptrd++) = val0; *(ptrd++) = val1; *(ptrd++) = val2; *(ptrd++) = val3; *(ptrd++) = val4; *(ptrd++) = val5;
+        *(ptrd++) = val6; *(ptrd++) = val7; *(ptrd++) = val8; *(ptrd++) = val9; *(ptrd++) = val10; *(ptrd++) = val11;
+      }
+      ptre+=11;
+      switch (ptre - ptrd) {
+      case 11 : *(--ptre) = val10;
+      case 10 : *(--ptre) = val9;
+      case 9 : *(--ptre) = val8;
+      case 8 : *(--ptre) = val7;
+      case 7 : *(--ptre) = val6;
+      case 6 : *(--ptre) = val5;
+      case 5 : *(--ptre) = val4;
+      case 4 : *(--ptre) = val3;
+      case 3 : *(--ptre) = val2;
+      case 2 : *(--ptre) = val1;
+      case 1 : *(--ptre) = 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,_spectrum).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 *ptrd, *ptre = end()-12;
+      for (ptrd = _data; ptrd<ptre; ) {
+        *(ptrd++) = val0; *(ptrd++) = val1; *(ptrd++) = val2; *(ptrd++) = val3; *(ptrd++) = val4; *(ptrd++) = val5;
+        *(ptrd++) = val6; *(ptrd++) = val7; *(ptrd++) = val8; *(ptrd++) = val9; *(ptrd++) = val10; *(ptrd++) = val11;
+        *(ptrd++) = val12;
+      }
+      ptre+=12;
+      switch (ptre - ptrd) {
+      case 12 : *(--ptre) = val11;
+      case 11 : *(--ptre) = val10;
+      case 10 : *(--ptre) = val9;
+      case 9 : *(--ptre) = val8;
+      case 8 : *(--ptre) = val7;
+      case 7 : *(--ptre) = val6;
+      case 6 : *(--ptre) = val5;
+      case 5 : *(--ptre) = val4;
+      case 4 : *(--ptre) = val3;
+      case 3 : *(--ptre) = val2;
+      case 2 : *(--ptre) = val1;
+      case 1 : *(--ptre) = 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,_spectrum).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 *ptrd, *ptre = end()-13;
+      for (ptrd = _data; ptrd<ptre; ) {
+        *(ptrd++) = val0; *(ptrd++) = val1; *(ptrd++) = val2; *(ptrd++) = val3; *(ptrd++) = val4; *(ptrd++) = val5;
+        *(ptrd++) = val6; *(ptrd++) = val7; *(ptrd++) = val8; *(ptrd++) = val9; *(ptrd++) = val10; *(ptrd++) = val11;
+        *(ptrd++) = val12; *(ptrd++) = val13;
+      }
+      ptre+=13;
+      switch (ptre - ptrd) {
+      case 13 : *(--ptre) = val12;
+      case 12 : *(--ptre) = val11;
+      case 11 : *(--ptre) = val10;
+      case 10 : *(--ptre) = val9;
+      case 9 : *(--ptre) = val8;
+      case 8 : *(--ptre) = val7;
+      case 7 : *(--ptre) = val6;
+      case 6 : *(--ptre) = val5;
+      case 5 : *(--ptre) = val4;
+      case 4 : *(--ptre) = val3;
+      case 3 : *(--ptre) = val2;
+      case 2 : *(--ptre) = val1;
+      case 1 : *(--ptre) = 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,_spectrum).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 *ptrd, *ptre = end()-14;
+      for (ptrd = _data; ptrd<ptre; ) {
+        *(ptrd++) = val0; *(ptrd++) = val1; *(ptrd++) = val2; *(ptrd++) = val3; *(ptrd++) = val4; *(ptrd++) = val5;
+        *(ptrd++) = val6; *(ptrd++) = val7; *(ptrd++) = val8; *(ptrd++) = val9; *(ptrd++) = val10; *(ptrd++) = val11;
+        *(ptrd++) = val12; *(ptrd++) = val13; *(ptrd++) = val14;
+      }
+      ptre+=14;
+      switch (ptre - ptrd) {
+      case 14 : *(--ptre) = val13;
+      case 13 : *(--ptre) = val12;
+      case 12 : *(--ptre) = val11;
+      case 11 : *(--ptre) = val10;
+      case 10 : *(--ptre) = val9;
+      case 9 : *(--ptre) = val8;
+      case 8 : *(--ptre) = val7;
+      case 7 : *(--ptre) = val6;
+      case 6 : *(--ptre) = val5;
+      case 5 : *(--ptre) = val4;
+      case 4 : *(--ptre) = val3;
+      case 3 : *(--ptre) = val2;
+      case 2 : *(--ptre) = val1;
+      case 1 : *(--ptre) = 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,_spectrum).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 *ptrd, *ptre = end()-15;
+      for (ptrd = _data; ptrd<ptre; ) {
+        *(ptrd++) = val0; *(ptrd++) = val1; *(ptrd++) = val2; *(ptrd++) = val3; *(ptrd++) = val4; *(ptrd++) = val5;
+        *(ptrd++) = val6; *(ptrd++) = val7; *(ptrd++) = val8; *(ptrd++) = val9; *(ptrd++) = val10; *(ptrd++) = val11;
+        *(ptrd++) = val12; *(ptrd++) = val13; *(ptrd++) = val14; *(ptrd++) = val15;
+      }
+      ptre+=15;
+      switch (ptre - ptrd) {
+      case 15 : *(--ptre) = val14;
+      case 14 : *(--ptre) = val13;
+      case 13 : *(--ptre) = val12;
+      case 12 : *(--ptre) = val11;
+      case 11 : *(--ptre) = val10;
+      case 10 : *(--ptre) = val9;
+      case 9 : *(--ptre) = val8;
+      case 8 : *(--ptre) = val7;
+      case 7 : *(--ptre) = val6;
+      case 6 : *(--ptre) = val5;
+      case 5 : *(--ptre) = val4;
+      case 4 : *(--ptre) = val3;
+      case 3 : *(--ptre) = val2;
+      case 2 : *(--ptre) = val1;
+      case 1 : *(--ptre) = 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,_spectrum).fill(val0,val1,val2,val3,val4,val5,val6,val7,val8,val9,val10,val11,val12,
+                                                  val13,val14,val15);
+    }
+
+    //! Fill image values according to the given expression, which can be a formula or a list of values.
+    CImg<T>& fill(const char *const expression, const bool repeat_flag) {
+      if (is_empty() || !expression || !*expression) return *this;
+      const unsigned int omode = cimg::exception_mode();
+      cimg::exception_mode() = 0;
+      try { // Try to fill values according to a formula.
+        const CImg<T> _base = std::strstr(expression,"i(")?+*this:CImg<T>(), &base = _base?_base:*this;
+        _cimg_math_parser mp(base,expression,"fill");
+        T *ptrd = _data;
+        cimg_forXYZC(*this,x,y,z,c) *(ptrd++) = (T)mp.eval((double)x,(double)y,(double)z,(double)c);
+      } catch (CImgException& e) { // If failed, try to recognize a list of values.
+        char item[16384] = { 0 }, sep = 0;
+        const char *nexpression = expression;
+        unsigned int nb = 0; const unsigned int siz = size();
+        T *ptrd = _data;
+        for (double val = 0; *nexpression && nb<siz; ++nb) {
+          sep = 0;
+          const int err = std::sscanf(nexpression,"%4095[ \n\t0-9.e+-]%c",item,&sep);
+          if (err>0 && std::sscanf(item,"%lf",&val)==1) {
+            nexpression+=std::strlen(item) + (err>1?1:0);
+            *(ptrd++) = (T)val;
+          } else break;
+        }
+        cimg::exception_mode() = omode;
+        if (nb<siz && (sep || *nexpression))
+          throw CImgArgumentException(e.what(),pixel_type(),expression);
+        if (repeat_flag && nb && nb<siz) for (T *ptrs = _data, *const ptre = _data + siz; ptrd<ptre; ++ptrs) *(ptrd++) = *ptrs;
+      }
+      cimg::exception_mode() = omode;
+      return *this;
+    }
+
+    CImg<T> get_fill(const char *const values, const bool repeat_values) const {
+      return (+*this).fill(values,repeat_values);
+    }
+
+    //! 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_values=true) {
+      if (is_empty() || !values) return *this;
+      T *ptrd = _data, *ptre = ptrd + size();
+      for (t *ptrs = values._data, *ptrs_end = ptrs + values.size(); ptrs<ptrs_end && ptrd<ptre; ++ptrs) *(ptrd++) = (T)*ptrs;
+      if (repeat_values && ptrd<ptre) for (T *ptrs = _data; ptrd<ptre; ++ptrs) *(ptrd++) = *ptrs;
+      return *this;
+    }
+
+    template<typename t>
+    CImg<T> get_fill(const CImg<t>& values, const bool repeat_values=true) const {
+      return repeat_values?CImg<T>(_width,_height,_depth,_spectrum).fill(values,repeat_values):(+*this).fill(values,repeat_values);
+    }
+
+    //! Fill image values along the X-axis at the specified pixel position (y,z,c).
+    CImg<T>& fillX(const unsigned int y, const unsigned int z, const unsigned int c, const int a0, ...) {
+#define _cimg_fill1(x,y,z,c,off,siz,t) { \
+    va_list ap; va_start(ap,a0); T *ptrd = data(x,y,z,c); *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 && c<_spectrum) _cimg_fill1(0,y,z,c,1,_width,int);
+      return *this;
+    }
+
+    CImg<T>& fillX(const unsigned int y, const unsigned int z, const unsigned int c, const double a0, ...) {
+      if (y<_height && z<_depth && c<_spectrum) _cimg_fill1(0,y,z,c,1,_width,double);
+      return *this;
+    }
+
+    //! Fill image values along the Y-axis at the specified pixel position (x,z,c).
+    CImg<T>& fillY(const unsigned int x, const unsigned int z, const unsigned int c, const int a0, ...) {
+      if (x<_width && z<_depth && c<_spectrum) _cimg_fill1(x,0,z,c,_width,_height,int);
+      return *this;
+    }
+
+    CImg<T>& fillY(const unsigned int x, const unsigned int z, const unsigned int c, const double a0, ...) {
+      if (x<_width && z<_depth && c<_spectrum) _cimg_fill1(x,0,z,c,_width,_height,double);
+      return *this;
+    }
+
+    //! Fill image values along the Z-axis at the specified pixel position (x,y,c).
+    CImg<T>& fillZ(const unsigned int x, const unsigned int y, const unsigned int c, const int a0, ...) {
+      const unsigned int wh = _width*_height;
+      if (x<_width && y<_height && c<_spectrum) _cimg_fill1(x,y,0,c,wh,_depth,int);
+      return *this;
+    }
+
+    CImg<T>& fillZ(const unsigned int x, const unsigned int y, const unsigned int c, const double a0, ...) {
+      const unsigned int wh = _width*_height;
+      if (x<_width && y<_height && c<_spectrum) _cimg_fill1(x,y,0,c,wh,_depth,double);
+      return *this;
+    }
+
+    //! Fill image values along the C-axis at the specified pixel position (x,y,z).
+    CImg<T>& fillC(const unsigned int x, const unsigned int y, const unsigned int z, const int a0, ...) {
+      const unsigned int whd = _width*_height*_depth;
+      if (x<_width && y<_height && z<_depth) _cimg_fill1(x,y,z,0,whd,_spectrum,int);
+      return *this;
+    }
+
+    CImg<T>& fillC(const unsigned int x, const unsigned int y, const unsigned int z, const double a0, ...) {
+      const unsigned int whd = _width*_height*_depth;
+      if (x<_width && y<_height && z<_depth) _cimg_fill1(x,y,z,0,whd,_spectrum,double);
+      return *this;
+    }
+
+    //! Remove specified value from the image buffer, and return resulting buffer as a one-column vector.
+    CImg<T>& discard(const T value) {
+      return get_discard(value).move_to(*this);
+    }
+
+    CImg<T> get_discard(const T value) const {
+      CImg<T> res(1,size());
+      T *pd = res._data;
+      for (const T *ps = _data, *const pse = end(); ps<pse; ++ps)
+        if (*ps!=value) *(pd++) = *ps;
+      if (pd==res._data) return CImg<T>();
+      return res.resize(1,pd-res._data,1,1,-1);
+    }
+
+    //! Remove specified values sequence from the image buffer, and return resulting buffer as a one-column vector.
+    template<typename t>
+    CImg<T>& discard(const CImg<t>& values) {
+      return get_discard(values).move_to(*this);
+    }
+
+    template<typename t>
+    CImg<T> get_discard(const CImg<t>& values) const {
+      if (!values) return *this;
+      if (values.size()==1) return get_discard(*values);
+      CImg<T> res(1,size());
+      T *pd = res._data;
+      const t *const pve = values.end();
+      for (const T *ps = _data, *const pse = end(); ps<pse; ) {
+        const T *_ps = ps;
+        const t *pv = values._data;
+        while (_ps<pse && pv<pve) { if (*(_ps++)!=(T)*pv) break; ++pv; }
+        if (pv!=pve) {
+          const unsigned int l = _ps - ps;
+          if (l==1) *(pd++) = *ps; else { std::memcpy(pd,ps,sizeof(T)*l); pd+=l; }
+        }
+        ps = _ps;
+      }
+      if (pd==res._data) return CImg<T>();
+      return res.resize(1,pd-res._data,1,1,-1);
+    }
+
+    //! Invert endianness of the image buffer.
+    CImg<T>& invert_endianness() {
+      cimg::invert_endianness(_data,size());
+      return *this;
+    }
+
+    CImg<T> get_invert_endianness() const {
+      return (+*this).invert_endianness();
+    }
+
+    //! Fill the image instance 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,ptrd,T) *ptrd = (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);
+    }
+
+    //! Compute image with rounded pixel values.
+    /**
+       \param y Rounding precision.
+       \param rounding_type Roundin type, can be 0 (nearest), 1 (forward), -1(backward).
+    **/
+    CImg<T>& round(const double y=1, const int rounding_type=0) {
+      if (y>0) cimg_for(*this,ptrd,T) *ptrd = cimg::round(*ptrd,y,rounding_type);
+      return *this;
+    }
+
+    CImg<T> get_round(const double y=1, const unsigned int rounding_type=0) const {
+      return (+*this).round(y,rounding_type);
+    }
+
+    //! Add random noise to the values of the image instance.
+    /**
+       \param sigma Amplitude of the random additive noise. If \p sigma<0, it stands for a percentage of the global value range.
+       \param noise_type Type of additive noise (can be \p 0=gaussian, \p 1=uniform, \p 2=Salt and Pepper, \p 3=Poisson or \p 4=Rician).
+       \return A reference to the modified image instance.
+       \note
+       - For Poisson noise (\p noise_type=3), parameter \p sigma is ignored, as Poisson noise only depends on the image value itself.
+       - Function \p CImg<T>::get_noise() is also defined. It returns a non-shared modified copy of the image instance.
+       \par Example
+       \code
+       const CImg<float> img("reference.jpg"), res = img.get_noise(40);
+       (img,res.normalize(0,255)).display();
+       \endcode
+       \image html ref_noise.jpg
+    **/
+    CImg<T>& noise(const double sigma, const unsigned int noise_type=0) {
+      if (!is_empty()) {
+        const Tfloat vmin = (Tfloat)cimg::type<T>::min(), vmax = (Tfloat)cimg::type<T>::max();
+        Tfloat nsigma = (Tfloat)sigma, m = 0, M = 0;
+        if (nsigma==0 && noise_type!=3) return *this;
+        if (nsigma<0 || noise_type==2) m = (Tfloat)min_max(M);
+        if (nsigma<0) nsigma = (Tfloat)(-nsigma*(M-m)/100.0);
+        switch (noise_type) {
+        case 0 : { // Gaussian noise
+          cimg_for(*this,ptrd,T) {
+            Tfloat val = (Tfloat)(*ptrd + nsigma*cimg::grand());
+            if (val>vmax) val = vmax;
+            if (val<vmin) val = vmin;
+            *ptrd = (T)val;
+          }
+        } break;
+        case 1 : { // Uniform noise
+          cimg_for(*this,ptrd,T) {
+            Tfloat val = (Tfloat)(*ptrd + nsigma*cimg::crand());
+            if (val>vmax) val = vmax;
+            if (val<vmin) val = vmin;
+            *ptrd = (T)val;
+          }
+        } break;
+        case 2 : { // Salt & Pepper noise
+          if (nsigma<0) nsigma = -nsigma;
+          if (M==m) { m = 0; M = (Tfloat)(cimg::type<T>::is_float()?1:cimg::type<T>::max()); }
+          cimg_for(*this,ptrd,T) if (cimg::rand()*100<nsigma) *ptrd = (T)(cimg::rand()<0.5?M:m);
+        } break;
+
+        case 3 : { // Poisson Noise
+          cimg_for(*this,ptrd,T) *ptrd = (T)cimg::prand(*ptrd);
+        } break;
+
+        case 4 : { // Rice noise
+          const Tfloat sqrt2 = (Tfloat)std::sqrt(2.0);
+          cimg_for(*this,ptrd,T) {
+            const Tfloat
+              val0 = (Tfloat)*ptrd/sqrt2,
+              re = (Tfloat)(val0 + nsigma*cimg::grand()),
+              im = (Tfloat)(val0 + nsigma*cimg::grand());
+            Tfloat val = (Tfloat)std::sqrt(re*re + im*im);
+            if (val>vmax) val = vmax;
+            if (val<vmin) val = vmin;
+            *ptrd = (T)val;
+          }
+        } break;
+        default :
+          throw CImgArgumentException(_cimg_instance
+                                      "noise() : Invalid specified noise type %d "
+                                      "(should be { 0=gaussian | 1=uniform | 2=salt&Pepper | 3=poisson }).",
+                                      cimg_instance,
+                                      noise_type);
+        }
+      }
+      return *this;
+    }
+
+    CImg<T> get_noise(const double sigma, const unsigned int noise_type=0) const {
+      return (+*this).noise(sigma,noise_type);
+    }
+
+    //! Linearly normalize values of the image instance between \p value_min and \p value_max.
+    /**
+       \param value_min Minimum desired value of the resulting image.
+       \param value_max Maximum desired value of the resulting image.
+       \return A reference to the modified image instance.
+       \note
+       - Function \p CImg<T>::get_normalize() is also defined. It returns a non-shared modified copy of the image instance.
+       \par Example
+       \code
+       const CImg<float> img("reference.jpg"), res = img.get_normalize(160,220);
+       (img,res).display();
+       \endcode
+       \image html ref_normalize2.jpg
+    **/
+    CImg<T>& normalize(const T value_min, const T value_max) {
+      if (is_empty()) return *this;
+      const T a = value_min<value_max?value_min:value_max, b = value_min<value_max?value_max:value_min;
+      T m, M = max_min(m);
+      const Tfloat fm = (Tfloat)m, fM = (Tfloat)M;
+      if (m==M) return fill(value_min);
+      if (m!=a || M!=b) cimg_for(*this,ptrd,T) *ptrd = (T)((*ptrd-fm)/(fM-fm)*(b-a)+a);
+      return *this;
+    }
+
+    CImg<Tfloat> get_normalize(const T value_min, const T value_max) const {
+      return CImg<Tfloat>(*this,false).normalize((Tfloat)value_min,(Tfloat)value_max);
+    }
+
+    //! Normalize multi-valued pixels of the image instance, with respect to their L2-norm.
+    /**
+       \return A reference to the modified image instance.
+       \note
+       - Function \p CImg<T>::get_normalize() is also defined. It returns a non-shared modified copy of the image instance.
+       \par Example
+       \code
+       const CImg<float> img("reference.jpg"), res = img.get_normalize();
+       (img,res.normalize(0,255)).display();
+       \endcode
+       \image html ref_normalize.jpg
+    **/
+    CImg<T>& normalize() {
+      T *ptrd = _data;
+      const unsigned int whd = _width*_height*_depth;
+      cimg_forXYZ(*this,x,y,z) {
+        const T *ptrs = ptrd;
+        float n = 0;
+        cimg_forC(*this,c) { n+=cimg::sqr((float)*ptrs); ptrs+=whd; }
+        n = (float)std::sqrt(n);
+        T *_ptrd = ptrd++;
+        if (n>0) cimg_forC(*this,c) { *_ptrd = (T)(*_ptrd/n); _ptrd+=whd; }
+        else cimg_forC(*this,c) { *_ptrd = (T)0; _ptrd+=whd; }
+      }
+      return *this;
+    }
+
+    CImg<Tfloat> get_normalize() const {
+      return CImg<Tfloat>(*this,false).normalize();
+    }
+
+    //! Compute L2-norm of each multi-valued pixel of the image instance.
+    /**
+       \param norm_type Type of computed vector norm (can be \p 0=Linf, \p 1=L1 or \p 2=L2).
+       \return A reference to the modified image instance.
+       \note
+       - Function \p CImg<T>::get_norm() is also defined. It returns a non-shared modified copy of the image instance.
+       \par Example
+       \code
+       const CImg<float> img("reference.jpg"), res = img.get_norm();
+       (img,res.normalize(0,255)).display();
+       \endcode
+       \image html ref_norm.jpg
+    **/
+    CImg<T>& norm(const int norm_type=2) {
+      if (_spectrum==1) return abs();
+      return get_norm(norm_type).move_to(*this);
+    }
+
+    CImg<Tfloat> get_norm(const int norm_type=2) const {
+      if (is_empty()) return *this;
+      if (_spectrum==1) return get_abs();
+      const T *ptrs = _data;
+      const unsigned int whd = _width*_height*_depth;
+      CImg<Tfloat> res(_width,_height,_depth);
+      Tfloat *ptrd = res._data;
+      switch (norm_type) {
+      case -1 : {             // Linf norm
+        cimg_forXYZ(*this,x,y,z) {
+          Tfloat n = 0;
+          const T *_ptrs = ptrs++;
+          cimg_forC(*this,c) { const Tfloat val = (Tfloat)cimg::abs(*_ptrs); if (val>n) n = val; _ptrs+=whd; }
+          *(ptrd++) = n;
+        }
+      } break;
+      case 1 : {              // L1 norm
+        cimg_forXYZ(*this,x,y,z) {
+          Tfloat n = 0;
+          const T *_ptrs = ptrs++;
+          cimg_forC(*this,c) { n+=cimg::abs(*_ptrs); _ptrs+=whd; }
+          *(ptrd++) = n;
+        }
+      } break;
+      default : {             // L2 norm
+        cimg_forXYZ(*this,x,y,z) {
+          Tfloat n = 0;
+          const T *_ptrs = ptrs++;
+          cimg_forC(*this,c) { n+=cimg::sqr((Tfloat)*_ptrs); _ptrs+=whd; }
+          *(ptrd++) = (Tfloat)std::sqrt((Tfloat)n);
+        }
+      }
+      }
+      return res;
+    }
+
+    //! Cut values of the image instance between \p value_min and \p value_max.
+    /**
+       \param value_min Minimum desired value of the resulting image.
+       \param value_max Maximum desired value of the resulting image.
+       \return A reference to the modified image instance.
+       \note
+       - Function \p CImg<T>::get_cut() is also defined. It returns a non-shared modified copy of the image instance.
+       \par Example
+       \code
+       const CImg<float> img("reference.jpg"), res = img.get_cut(160,220);
+       (img,res).display();
+       \endcode
+       \image html ref_cut.jpg
+    **/
+    CImg<T>& cut(const T value_min, const T value_max) {
+      if (is_empty()) return *this;
+      const T a = value_min<value_max?value_min:value_max, b = value_min<value_max?value_max:value_min;
+      cimg_for(*this,ptrd,T) *ptrd = (*ptrd<a)?a:((*ptrd>b)?b:*ptrd);
+      return *this;
+    }
+
+    CImg<T> get_cut(const T value_min, const T value_max) const {
+      return (+*this).cut(value_min,value_max);
+    }
+
+    //! Uniformly quantize values of the image instance into \p nb_levels levels.
+    /**
+       \param nb_levels Number of quantization levels.
+       \param keep_range Tells if resulting values keep the same range as the original ones.
+       \return A reference to the modified image instance.
+       \note
+       - Function \p CImg<T>::get_quantize() is also defined. It returns a non-shared modified copy of the image instance.
+       \par Example
+       \code
+       const CImg<float> img("reference.jpg"), res = img.get_quantize(4);
+       (img,res).display();
+       \endcode
+       \image html ref_quantize.jpg
+    **/
+    CImg<T>& quantize(const unsigned int nb_levels, const bool keep_range=true) {
+      if (!nb_levels)
+        throw CImgArgumentException(_cimg_instance
+                                    "quantize() : Invalid quantization request with 0 values.",
+                                    cimg_instance);
+
+      if (is_empty()) return *this;
+      Tfloat m, M = (Tfloat)max_min(m), range = M - m;
+      if (range>0) {
+        if (keep_range) cimg_for(*this,ptrd,T) {
+          const unsigned int val = (unsigned int)((*ptrd-m)*nb_levels/range);
+          *ptrd = (T)(m + cimg::min(val,nb_levels-1)*range/nb_levels);
+        } else cimg_for(*this,ptrd,T) {
+          const unsigned int val = (unsigned int)((*ptrd-m)*nb_levels/range);
+          *ptrd = (T)cimg::min(val,nb_levels-1);
+        }
+      }
+      return *this;
+    }
+
+    CImg<T> get_quantize(const unsigned int n, const bool keep_range=true) const {
+      return (+*this).quantize(n,keep_range);
+    }
+
+    //! Threshold values of the image instance.
+    /**
+       \param value Threshold value
+       \param soft_threshold Tells if soft thresholding must be applied (instead of hard one).
+       \param strict_threshold Tells if threshold value is strict.
+       \return A reference to the modified image instance. Resulting pixel values are either equal to 0 or 1.
+       \note
+       - Function \p CImg<T>::get_threshold() is also defined. It returns a non-shared modified copy of the image instance.
+       \par Example
+       \code
+       const CImg<float> img("reference.jpg"), res = img.get_threshold(128);
+       (img,res.normalize(0,255)).display();
+       \endcode
+       \image html ref_threshold.jpg
+    **/
+    CImg<T>& threshold(const T value, const bool soft_threshold=false, const bool strict_threshold=false) {
+      if (is_empty()) return *this;
+      if (strict_threshold) {
+        if (soft_threshold) cimg_for(*this,ptrd,T) { const T v = *ptrd; *ptrd = v>value?(T)(v-value):v<-(float)value?(T)(v+value):(T)0; }
+        else cimg_for(*this,ptrd,T) *ptrd = *ptrd>value?(T)1:(T)0;
+      } else {
+        if (soft_threshold) cimg_for(*this,ptrd,T) { const T v = *ptrd; *ptrd = v>=value?(T)(v-value):v<=-(float)value?(T)(v+value):(T)0; }
+        else cimg_for(*this,ptrd,T) *ptrd = *ptrd>=value?(T)1:(T)0;
+      }
+      return *this;
+    }
+
+    CImg<T> get_threshold(const T value, const bool soft_threshold=false, const bool strict_threshold=false) const {
+      return (+*this).threshold(value,soft_threshold,strict_threshold);
+    }
+
+    //! Compute the histogram of the image instance.
+    /**
+       \param nb_levels Number of desired histogram levels.
+       \param value_min Minimum pixel value considered for the histogram computation. All pixel values lower than \p value_min will not be counted.
+       \param value_max Maximum pixel value considered for the histogram computation. All pixel values higher than \p value_max will not be counted.
+       \return image instance is replaced by its histogram, defined as a \p CImg<T>(nb_levels) image.
+       \note
+       - The histogram H of an image I is the 1d function where H(x) counts the number of occurences of the value x in the image I.
+       - If \p value_min==value_max==0 (default behavior), the function first estimates the whole range of pixel values
+       then uses it to compute the histogram.
+       - The resulting histogram is always defined in 1d. Histograms of multi-valued images are not multi-dimensional.
+       - Function \p CImg<T>::get_histogram() is also defined. It returns a non-shared modified copy of the image instance.
+       \par Example
+       \code
+       const CImg<float> img = CImg<float>("reference.jpg").histogram(256);
+       img.display_graph(0,3);
+       \endcode
+       \image html ref_histogram.jpg
+    **/
+    CImg<T>& histogram(const unsigned int nb_levels, const T value_min=(T)0, const T value_max=(T)0) {
+      return get_histogram(nb_levels,value_min,value_max).move_to(*this);
+    }
+
+    CImg<floatT> get_histogram(const unsigned int nb_levels, const T value_min=(T)0, const T value_max=(T)0) const {
+      if (!nb_levels || is_empty()) return CImg<floatT>();
+      T vmin = value_min<value_max?value_min:value_max, vmax = value_min<value_max?value_max:value_min;
+      if (vmin==vmax && vmin==0) vmin = min_max(vmax);
+      CImg<floatT> res(nb_levels,1,1,1,0);
+      cimg_for(*this,ptrs,T) {
+        const T val = *ptrs;
+        if (val>=vmin && val<=vmax) ++res[val==vmax?nb_levels-1:(unsigned int)((val-vmin)*nb_levels/(vmax-vmin))];
+      }
+      return res;
+    }
+
+    //! Compute the histogram-equalized version of the image instance.
+    /**
+       \param nb_levels Number of histogram levels used for the equalization.
+       \param value_min Minimum pixel value considered for the histogram computation. All pixel values lower than \p value_min will not be counted.
+       \param value_max Maximum pixel value considered for the histogram computation. All pixel values higher than \p value_max will not be counted.
+       \return A reference to the modified image instance.
+       \note
+       - If \p value_min==value_max==0 (default behavior), the function first estimates the whole range of pixel values
+       then uses it to equalize the histogram.
+       - Function \p CImg<T>::get_equalize() is also defined. It returns a non-shared modified copy of the image instance.
+       \par Example
+       \code
+       const CImg<float> img("reference.jpg"), res = img.get_equalize(256);
+       (img,res).display();
+       \endcode
+       \image html ref_equalize.jpg
+    **/
+    CImg<T>& equalize(const unsigned int nb_levels, const T value_min=(T)0, const T value_max=(T)0) {
+      if (is_empty()) return *this;
+      T vmin = value_min, vmax = value_max;
+      if (vmin==vmax && vmin==0) vmin = min_max(vmax);
+      if (vmin<vmax) {
+        CImg<floatT> hist = get_histogram(nb_levels,vmin,vmax);
+        float cumul = 0;
+        cimg_forX(hist,pos) { cumul+=hist[pos]; hist[pos] = cumul; }
+        cimg_for(*this,ptrd,T) {
+          const int pos = (unsigned int)((*ptrd-vmin)*(nb_levels-1)/(vmax-vmin));
+          if (pos>=0 && pos<(int)nb_levels) *ptrd = (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);
+    }
+
+    //! Index multi-valued pixels of the image instance, regarding to a predefined palette.
+    /**
+       \param palette Multi-valued palette used as the basis for multi-valued pixel indexing.
+       \param dithering Level of dithering (0=disable, 1=standard level).
+       \param map_indexes Tell if the values of the resulting image are the palette indices or the palette vectors.
+       \return A reference to the modified image instance.
+       \note
+       - \p img.index(palette,dithering,1) is equivalent to <tt>img.index(palette,dithering,0).map(palette)</tt>.
+       - Function \p CImg<T>::get_index() is also defined. It returns a non-shared modified copy of the image instance.
+       \par Example
+       \code
+       const CImg<float> img("reference.jpg"), palette(3,1,1,3, 0,128,255, 0,128,255, 0,128,255);
+       const CImg<float> res = img.get_index(palette,1,true);
+       (img,res).display();
+       \endcode
+       \image html ref_index.jpg
+    **/
+    template<typename t>
+    CImg<T>& index(const CImg<t>& palette, const float dithering=1, const bool map_indexes=false) {
+      return get_index(palette,dithering,map_indexes).move_to(*this);
+    }
+
+    template<typename t>
+    CImg<typename CImg<t>::Tuint>
+    get_index(const CImg<t>& palette, const float dithering=1, const bool map_indexes=true) const {
+      if (palette._spectrum!=_spectrum)
+        throw CImgArgumentException(_cimg_instance
+                                    "index() : Instance and specified palette (%u,%u,%u,%u,%p) "
+                                    "have incompatible dimensions.",
+                                    cimg_instance,
+                                    palette._width,palette._height,palette._depth,palette._spectrum,palette._data);
+
+      typedef typename CImg<t>::Tuint tuint;
+      if (is_empty()) return CImg<tuint>();
+      const unsigned int whd = _width*_height*_depth, pwhd = palette._width*palette._height*palette._depth;
+      CImg<tuint> res(_width,_height,_depth,map_indexes?_spectrum:1);
+      tuint *ptrd = res._data;
+      if (dithering>0) { // Dithered versions.
+        const float ndithering = (dithering<0?0:dithering>1?1:dithering)/16;
+        Tfloat valm = 0, valM = (Tfloat)max_min(valm);
+        if (valm==valM && valm>=0 && valM<=255) { valm = 0; valM = 255; }
+        CImg<Tfloat> cache = get_crop(-1,0,0,0,_width,1,0,_spectrum-1);
+        Tfloat *cache_current = cache.data(1,0,0,0), *cache_next = cache.data(1,1,0,0);
+        const unsigned int cwhd = cache._width*cache._height*cache._depth;
+        switch (_spectrum) {
+        case 1 : { // Optimized for scalars.
+          cimg_forYZ(*this,y,z) {
+            if (y<height()-2) {
+              Tfloat *ptrc0 = cache_next; const T *ptrs0 = data(0,y+1,z,0);
+              cimg_forX(*this,x) *(ptrc0++) = (Tfloat)*(ptrs0++);
+            }
+            Tfloat *ptrs0 = cache_current, *ptrsn0 = cache_next;
+            cimg_forX(*this,x) {
+              const Tfloat _val0 = (Tfloat)*ptrs0, val0 = _val0<valm?valm:_val0>valM?valM:_val0;
+              Tfloat distmin = cimg::type<Tfloat>::max(); const t *ptrmin0 = palette._data;
+              for (const t *ptrp0 = palette._data, *ptrp_end = ptrp0 + pwhd; ptrp0<ptrp_end; ) {
+                const Tfloat pval0 = (Tfloat)*(ptrp0++) - val0, dist = pval0*pval0;
+                if (dist<distmin) { ptrmin0 = ptrp0 - 1; distmin = dist; }
+              }
+              const Tfloat err0 = ((*(ptrs0++)=val0) - (Tfloat)*ptrmin0)*ndithering;
+              *ptrs0+=7*err0; *(ptrsn0-1)+=3*err0; *(ptrsn0++)+=5*err0; *ptrsn0+=err0;
+              if (map_indexes) *(ptrd++) = (tuint)*ptrmin0; else *(ptrd++) = (tuint)(ptrmin0 - palette._data);
+            }
+            cimg::swap(cache_current,cache_next);
+          }
+        } break;
+        case 2 : { // Optimized for 2d vectors.
+          tuint *ptrd1 = ptrd + whd;
+          cimg_forYZ(*this,y,z) {
+            if (y<height()-2) {
+              Tfloat *ptrc0 = cache_next, *ptrc1 = ptrc0 + cwhd;
+              const T *ptrs0 = data(0,y+1,z,0), *ptrs1 = ptrs0 + whd;
+              cimg_forX(*this,x) { *(ptrc0++) = (Tfloat)*(ptrs0++); *(ptrc1++) = (Tfloat)*(ptrs1++); }
+            }
+            Tfloat
+              *ptrs0 = cache_current, *ptrs1 = ptrs0 + cwhd,
+              *ptrsn0 = cache_next, *ptrsn1 = ptrsn0 + cwhd;
+            cimg_forX(*this,x) {
+              const Tfloat
+                _val0 = (Tfloat)*ptrs0, val0 = _val0<valm?valm:_val0>valM?valM:_val0,
+                _val1 = (Tfloat)*ptrs1, val1 = _val1<valm?valm:_val1>valM?valM:_val1;
+              Tfloat distmin = cimg::type<Tfloat>::max(); const t *ptrmin0 = palette._data;
+              for (const t *ptrp0 = palette._data, *ptrp1 = ptrp0 + pwhd, *ptrp_end = ptrp1; ptrp0<ptrp_end; ) {
+                const Tfloat
+                  pval0 = (Tfloat)*(ptrp0++) - val0, pval1 = (Tfloat)*(ptrp1++) - val1,
+                  dist = pval0*pval0 + pval1*pval1;
+                if (dist<distmin) { ptrmin0 = ptrp0 - 1; distmin = dist; }
+              }
+              const t *const ptrmin1 = ptrmin0 + pwhd;
+              const Tfloat
+                err0 = ((*(ptrs0++)=val0) - (Tfloat)*ptrmin0)*ndithering,
+                err1 = ((*(ptrs1++)=val1) - (Tfloat)*ptrmin1)*ndithering;
+              *ptrs0+=7*err0; *ptrs1+=7*err1;
+              *(ptrsn0-1)+=3*err0; *(ptrsn1-1)+=3*err1;
+              *(ptrsn0++)+=5*err0; *(ptrsn1++)+=5*err1;
+              *ptrsn0+=err0; *ptrsn1+=err1;
+              if (map_indexes) { *(ptrd++) = (tuint)*ptrmin0; *(ptrd1++) = (tuint)*ptrmin1; }
+              else *(ptrd++) = (tuint)(ptrmin0 - palette._data);
+            }
+            cimg::swap(cache_current,cache_next);
+          }
+        } break;
+        case 3 : { // Optimized for 3d vectors (colors).
+          tuint *ptrd1 = ptrd + whd, *ptrd2 = ptrd1 + whd;
+          cimg_forYZ(*this,y,z) {
+            if (y<height()-2) {
+              Tfloat *ptrc0 = cache_next, *ptrc1 = ptrc0 + cwhd, *ptrc2 = ptrc1 + cwhd;
+              const T *ptrs0 = data(0,y+1,z,0), *ptrs1 = ptrs0 + whd, *ptrs2 = ptrs1 + whd;
+              cimg_forX(*this,x) { *(ptrc0++) = (Tfloat)*(ptrs0++); *(ptrc1++) = (Tfloat)*(ptrs1++); *(ptrc2++) = (Tfloat)*(ptrs2++); }
+            }
+            Tfloat
+              *ptrs0 = cache_current, *ptrs1 = ptrs0 + cwhd, *ptrs2 = ptrs1 + cwhd,
+              *ptrsn0 = cache_next, *ptrsn1 = ptrsn0 + cwhd, *ptrsn2 = ptrsn1 + cwhd;
+            cimg_forX(*this,x) {
+              const Tfloat
+                _val0 = (Tfloat)*ptrs0, val0 = _val0<valm?valm:_val0>valM?valM:_val0,
+                _val1 = (Tfloat)*ptrs1, val1 = _val1<valm?valm:_val1>valM?valM:_val1,
+                _val2 = (Tfloat)*ptrs2, val2 = _val2<valm?valm:_val2>valM?valM:_val2;
+              Tfloat distmin = cimg::type<Tfloat>::max(); const t *ptrmin0 = palette._data;
+              for (const t *ptrp0 = palette._data, *ptrp1 = ptrp0 + pwhd, *ptrp2 = ptrp1 + pwhd, *ptrp_end = ptrp1; ptrp0<ptrp_end; ) {
+                const Tfloat
+                  pval0 = (Tfloat)*(ptrp0++) - val0, pval1 = (Tfloat)*(ptrp1++) - val1, pval2 = (Tfloat)*(ptrp2++) - val2,
+                  dist = pval0*pval0 + pval1*pval1 + pval2*pval2;
+                if (dist<distmin) { ptrmin0 = ptrp0 - 1; distmin = dist; }
+              }
+              const t *const ptrmin1 = ptrmin0 + pwhd, *const ptrmin2 = ptrmin1 + pwhd;
+              const Tfloat
+                err0 = ((*(ptrs0++)=val0) - (Tfloat)*ptrmin0)*ndithering,
+                err1 = ((*(ptrs1++)=val1) - (Tfloat)*ptrmin1)*ndithering,
+                err2 = ((*(ptrs2++)=val2) - (Tfloat)*ptrmin2)*ndithering;
+
+              *ptrs0+=7*err0; *ptrs1+=7*err1; *ptrs2+=7*err2;
+              *(ptrsn0-1)+=3*err0; *(ptrsn1-1)+=3*err1; *(ptrsn2-1)+=3*err2;
+              *(ptrsn0++)+=5*err0; *(ptrsn1++)+=5*err1; *(ptrsn2++)+=5*err2;
+              *ptrsn0+=err0; *ptrsn1+=err1; *ptrsn2+=err2;
+
+              if (map_indexes) { *(ptrd++) = (tuint)*ptrmin0; *(ptrd1++) = (tuint)*ptrmin1; *(ptrd2++) = (tuint)*ptrmin2; }
+              else *(ptrd++) = (tuint)(ptrmin0 - palette._data);
+            }
+            cimg::swap(cache_current,cache_next);
+          }
+        } break;
+        default : // Generic version
+          cimg_forYZ(*this,y,z) {
+            if (y<height()-2) {
+              Tfloat *ptrc = cache_next;
+              cimg_forC(*this,c) {
+                Tfloat *_ptrc = ptrc; const T *_ptrs = data(0,y+1,z,c);
+                cimg_forX(*this,x) *(_ptrc++) = (Tfloat)*(_ptrs++);
+                ptrc+=cwhd;
+              }
+            }
+            Tfloat *ptrs = cache_current, *ptrsn = cache_next;
+            cimg_forX(*this,x) {
+              Tfloat distmin = cimg::type<Tfloat>::max(); const t *ptrmin = palette._data;
+              for (const t *ptrp = palette._data, *ptrp_end = ptrp + pwhd; ptrp<ptrp_end; ++ptrp) {
+                Tfloat dist = 0; Tfloat *_ptrs = ptrs; const t *_ptrp = ptrp;
+                cimg_forC(*this,c) {
+                  const Tfloat _val = *_ptrs, val = _val<valm?valm:_val>valM?valM:_val;
+                  dist+=cimg::sqr((*_ptrs=val) - (Tfloat)*_ptrp); _ptrs+=cwhd; _ptrp+=pwhd;
+                }
+                if (dist<distmin) { ptrmin = ptrp; distmin = dist; }
+              }
+              const t *_ptrmin = ptrmin; Tfloat *_ptrs = ptrs++, *_ptrsn = (ptrsn++)-1;
+              cimg_forC(*this,c) {
+                const Tfloat err = (*(_ptrs++) - (Tfloat)*_ptrmin)*ndithering;
+                *_ptrs+=7*err; *(_ptrsn++)+=3*err; *(_ptrsn++)+=5*err; *_ptrsn+=err;
+                _ptrmin+=pwhd; _ptrs+=cwhd-1; _ptrsn+=cwhd-2;
+              }
+              if (map_indexes) {
+                tuint *_ptrd = ptrd++;
+                cimg_forC(*this,c) { *_ptrd = (tuint)*ptrmin; _ptrd+=whd; ptrmin+=pwhd; }
+              }
+              else *(ptrd++) = (tuint)(ptrmin - palette._data);
+            }
+            cimg::swap(cache_current,cache_next);
+          }
+        }
+      } else { // Non-dithered versions
+        switch (_spectrum) {
+        case 1 : { // Optimized for scalars.
+          for (const T *ptrs0 = _data, *ptrs_end = ptrs0 + whd; ptrs0<ptrs_end; ) {
+            const Tfloat val0 = (Tfloat)*(ptrs0++);
+            Tfloat distmin = cimg::type<Tfloat>::max(); const t *ptrmin0 = palette._data;
+            for (const t *ptrp0 = palette._data, *ptrp_end = ptrp0 + pwhd; ptrp0<ptrp_end; ) {
+              const Tfloat pval0 = (Tfloat)*(ptrp0++) - val0, dist = pval0*pval0;
+              if (dist<distmin) { ptrmin0 = ptrp0 - 1; distmin = dist; }
+            }
+            if (map_indexes) *(ptrd++) = (tuint)*ptrmin0; else *(ptrd++) = (tuint)(ptrmin0 - palette._data);
+          }
+        } break;
+        case 2 : { // Optimized for 2d vectors.
+          tuint *ptrd1 = ptrd + whd;
+          for (const T *ptrs0 = _data, *ptrs1 = ptrs0 + whd, *ptrs_end = ptrs1; ptrs0<ptrs_end; ) {
+            const Tfloat val0 = (Tfloat)*(ptrs0++), val1 = (Tfloat)*(ptrs1++);
+            Tfloat distmin = cimg::type<Tfloat>::max(); const t *ptrmin0 = palette._data;
+            for (const t *ptrp0 = palette._data, *ptrp1 = ptrp0 + pwhd, *ptrp_end = ptrp1; ptrp0<ptrp_end; ) {
+              const Tfloat
+                pval0 = (Tfloat)*(ptrp0++) - val0, pval1 = (Tfloat)*(ptrp1++) - val1,
+                dist = pval0*pval0 + pval1*pval1;
+              if (dist<distmin) { ptrmin0 = ptrp0 - 1; distmin = dist; }
+            }
+            if (map_indexes) { *(ptrd++) = (tuint)*ptrmin0; *(ptrd1++) = (tuint)*(ptrmin0 + pwhd); }
+            else *(ptrd++) = (tuint)(ptrmin0 - palette._data);
+          }
+        } break;
+        case 3 : { // Optimized for 3d vectors (colors).
+          tuint *ptrd1 = ptrd + whd, *ptrd2 = ptrd1 + whd;
+          for (const T *ptrs0 = _data, *ptrs1 = ptrs0 + whd, *ptrs2 = ptrs1 + whd, *ptrs_end = ptrs1; ptrs0<ptrs_end; ) {
+            const Tfloat val0 = (Tfloat)*(ptrs0++), val1 = (Tfloat)*(ptrs1++), val2 = (Tfloat)*(ptrs2++);
+            Tfloat distmin = cimg::type<Tfloat>::max(); const t *ptrmin0 = palette._data;
+            for (const t *ptrp0 = palette._data, *ptrp1 = ptrp0 + pwhd, *ptrp2 = ptrp1 + pwhd, *ptrp_end = ptrp1; ptrp0<ptrp_end; ) {
+              const Tfloat
+                pval0 = (Tfloat)*(ptrp0++) - val0, pval1 = (Tfloat)*(ptrp1++) - val1, pval2 = (Tfloat)*(ptrp2++) - val2,
+                dist = pval0*pval0 + pval1*pval1 + pval2*pval2;
+              if (dist<distmin) { ptrmin0 = ptrp0 - 1; distmin = dist; }
+            }
+            if (map_indexes) { *(ptrd++) = (tuint)*ptrmin0; *(ptrd1++) = (tuint)*(ptrmin0 + pwhd); *(ptrd2++) = (tuint)*(ptrmin0 + 2*pwhd); }
+            else *(ptrd++) = (tuint)(ptrmin0 - palette._data);
+          }
+        } break;
+        default : // Generic version.
+          for (const T *ptrs = _data, *ptrs_end = ptrs + whd; ptrs<ptrs_end; ++ptrs) {
+            Tfloat distmin = cimg::type<Tfloat>::max(); const t *ptrmin = palette._data;
+            for (const t *ptrp = palette._data, *ptrp_end = ptrp + pwhd; ptrp<ptrp_end; ++ptrp) {
+              Tfloat dist = 0; const T *_ptrs = ptrs; const t *_ptrp = ptrp;
+              cimg_forC(*this,c) { dist+=cimg::sqr((Tfloat)*_ptrs - (Tfloat)*_ptrp); _ptrs+=whd; _ptrp+=pwhd; }
+              if (dist<distmin) { ptrmin = ptrp; distmin = dist; }
+            }
+            if (map_indexes) {
+              tuint *_ptrd = ptrd++;
+              cimg_forC(*this,c) { *_ptrd = (tuint)*ptrmin; _ptrd+=whd; ptrmin+=pwhd; }
+            }
+            else *(ptrd++) = (tuint)(ptrmin - palette._data);
+          }
+        }
+      }
+      return res;
+    }
+
+    //! Map predefined palette on the scalar (indexed) image instance.
+    /**
+       \param palette Multi-valued palette used for mapping the indexes.
+       \return A reference to the modified image instance.
+       \note
+       - Function \p CImg<T>::get_map() is also defined. It returns a non-shared modified copy of the image instance.
+       \par Example
+       \code
+       const CImg<float> img("reference.jpg"),
+                         palette1(3,1,1,3, 0,128,255, 0,128,255, 0,128,255),
+                         palette2(3,1,1,3, 255,0,0, 0,255,0, 0,0,255),
+                         res = img.get_index(palette1,0).map(palette2);
+       (img,res).display();
+       \endcode
+       \image html ref_map.jpg
+    **/
+    template<typename t>
+    CImg<T>& map(const CImg<t>& palette) {
+      return get_map(palette).move_to(*this);
+    }
+
+    template<typename t>
+    CImg<t> get_map(const CImg<t>& palette) const {
+      if (_spectrum!=1 && palette._spectrum!=1)
+        throw CImgArgumentException(_cimg_instance
+                                    "map() : Instance and specified palette (%u,%u,%u,%u,%p) "
+                                    "have incompatible dimensions.",
+                                    cimg_instance,
+                                    palette._width,palette._height,palette._depth,palette._spectrum,palette._data);
+
+      const unsigned int whd = _width*_height*_depth, pwhd = palette._width*palette._height*palette._depth;
+      CImg<t> res(_width,_height,_depth,palette._spectrum==1?_spectrum:palette._spectrum);
+      switch (palette._spectrum) {
+      case 1 : { // Optimized for scalars.
+        const T *ptrs = _data + whd*_spectrum;
+        cimg_for(res,ptrd,t) {
+          const unsigned int _ind = (unsigned int)*(--ptrs), ind = _ind<pwhd?_ind:0;
+          *ptrd = palette[ind];
+        }
+      } break;
+      case 2 : { // Optimized for 2d vectors.
+        const t *const ptrp0 = palette._data, *ptrp1 = ptrp0 + pwhd;
+        t *ptrd0 = res._data, *ptrd1 = ptrd0 + whd;
+        for (const T *ptrs = _data, *ptrs_end = ptrs + whd; ptrs<ptrs_end; ) {
+          const unsigned int _ind = (unsigned int)*(ptrs++), ind = _ind<pwhd?_ind:0;
+          *(ptrd0++) = ptrp0[ind]; *(ptrd1++) = ptrp1[ind];
+        }
+      } break;
+      case 3 : { // Optimized for 3d vectors (colors).
+        const t *const ptrp0 = palette._data, *ptrp1 = ptrp0 + pwhd, *ptrp2 = ptrp1 + pwhd;
+        t *ptrd0 = res._data, *ptrd1 = ptrd0 + whd, *ptrd2 = ptrd1 + whd;
+        for (const T *ptrs = _data, *ptrs_end = ptrs + whd; ptrs<ptrs_end; ) {
+          const unsigned int _ind = (unsigned int)*(ptrs++), ind = _ind<pwhd?_ind:0;
+          *(ptrd0++) = ptrp0[ind]; *(ptrd1++) = ptrp1[ind]; *(ptrd2++) = ptrp2[ind];
+        }
+      } break;
+      default : { // Generic version.
+        t *ptrd = res._data;
+        for (const T *ptrs = _data, *ptrs_end = ptrs + whd; ptrs<ptrs_end; ) {
+          const unsigned int _ind = (unsigned int)*(ptrs++), ind = _ind<pwhd?_ind:0;
+          const t *ptrp = palette._data + ind;
+          t *_ptrd = ptrd++; cimg_forC(res,c) { *_ptrd = *ptrp; _ptrd+=whd; ptrp+=pwhd; }
+        }
+      }
+      }
+      return res;
+    }
+
+    //! Label connected components.
+    /**
+       \param is_high_connectivity Boolean that choose between 4(false)- or 8(true)-connectivity
+       in 2d case, and between 6(false)- or 26(true)-connectivity in 3d case.
+       \note The algorithm of connected components computation has been primarily done
+       by A. Meijster, according to the publication :
+       'W.H. Hesselink, A. Meijster, C. Bron, "Concurrent Determination of Connected Components.",
+       In: Science of Computer Programming 41 (2001), pp. 173--194'.
+       The submitted code has then been modified to fit CImg coding style and constraints.
+    **/
+    CImg<T>& label(const bool is_high_connectivity=false, const Tfloat
+                   tolerance=0) {
+      return get_label(is_high_connectivity,tolerance).move_to(*this);
+    }
+
+    CImg<unsigned long> get_label(const bool is_high_connectivity=false,
+                                  const Tfloat tolerance=0) const {
+      if (is_empty()) return CImg<unsigned long>();
+
+      // Create neighborhood tables.
+      int dx[13], dy[13], dz[13], nb = 0;
+      dx[nb]=1; dy[nb] = 0; dz[nb++]=0;
+      dx[nb]=0; dy[nb] = 1; dz[nb++]=0;
+      if (is_high_connectivity) {
+        dx[nb]=1; dy[nb] = 1; dz[nb++]=0;
+        dx[nb]=1; dy[nb] = -1; dz[nb++]=0;
+      }
+      if (_depth>1) { // 3d version.
+        dx[nb]=0; dy[nb] = 0; dz[nb++]=1;
+        if (is_high_connectivity) {
+          dx[nb]=1; dy[nb] = 1; dz[nb++]=-1;
+          dx[nb]=1; dy[nb] = 0; dz[nb++]=-1;
+          dx[nb]=1; dy[nb] = -1; dz[nb++]=-1;
+          dx[nb]=0; dy[nb] = 1; dz[nb++]=-1;
+
+          dx[nb]=0; dy[nb] = 1; dz[nb++]=1;
+          dx[nb]=1; dy[nb] = -1; dz[nb++]=1;
+          dx[nb]=1; dy[nb] = 0; dz[nb++]=1;
+          dx[nb]=1; dy[nb] = 1; dz[nb++]=1;
+        }
+      }
+      return _get_label(nb,dx,dy,dz,tolerance);
+    }
+
+    template<typename t>
+    CImg<T>& label(const CImg<t>& connectivity_mask, const Tfloat tolerance=0) {
+      return get_label(connectivity_mask,tolerance).move_to(*this);
+    }
+
+    template<typename t>
+    CImg<unsigned long> get_label(const CImg<t>& connectivity_mask,
+                                  const Tfloat tolerance=0) const {
+      int nb = 0;
+      cimg_for(connectivity_mask,ptr,t) if (*ptr) ++nb;
+      CImg<intT> dx(nb,1,1,1,0), dy(nb,1,1,1,0), dz(nb,1,1,1,0);
+      nb = 0;
+      cimg_forXYZ(connectivity_mask,x,y,z) if ((x || y || z) &&
+                                               connectivity_mask(x,y,z)) {
+        dx[nb] = x; dy[nb] = y; dz[nb++] = z;
+      }
+      return _get_label(nb,dx,dy,dz,tolerance);
+    }
+
+    // Generic version, allows any kind of neighbor connectivity. Use it at your own risk :)
+    CImg<unsigned long> _get_label(const unsigned int nb, const int
+                                   *const dx, const int *const dy, const int *const dz,
+                                   const Tfloat tolerance) const {
+      CImg<unsigned long> res(_width,_height,_depth,_spectrum);
+      cimg_forC(*this,c) {
+        CImg<unsigned long> _res = res.get_shared_channel(c);
+
+        // Init label numbers.
+        unsigned long *ptr = _res.data();
+        cimg_foroff(_res,p) *(ptr++) = p;
+
+        // For each neighbour-direction, label.
+        for (unsigned int n = 0; n<nb; ++n) {
+          const int _dx = dx[n], _dy = dy[n], _dz = dz[n];
+          if (_dx || _dy || _dz) {
+            const int
+              x0 = _dx<0?-_dx:0,
+              x1 = _dx<0?_width:_width - _dx,
+              y0 = _dy<0?-_dy:0,
+              y1 = _dy<0?_height:_height - _dy,
+              z0 = _dz<0?-_dz:0,
+              z1 = _dz<0?_depth:_depth - _dz;
+            const long wh = _width*_height;
+            const long offset = (long)_dz*wh + (long)_dy*_width + _dx;
+            for (long z = z0, nz = z0 + _dz, pz = z0*wh; z<z1; ++z, ++nz, pz+=wh) {
+              for (long y = y0, ny = y0 + _dy, py = y0*_width + pz; y<y1; ++y, ++ny, py+=_width) {
+                for (long x = x0, nx = x0 + _dx, p = x0 + py; x<x1; ++x, ++nx, ++p) {
+                  if ((Tfloat)cimg::abs((*this)(x,y,z,c,wh)-(*this)(nx,ny,nz,c,wh))<=tolerance) {
+                    const long q = p + offset;
+                    unsigned long x, y;
+                    for (x = p<q?q:p, y = p<q?p:q; x!=y && _res[x]!=x; ) { x = _res[x]; if (x<y) cimg::swap(x,y); }
+                    if (x!=y) _res[x] = y;
+                    for (unsigned long _p = p; _p!=y; ) { const unsigned long h = _res[_p]; _res[_p] = y; _p = h; }
+                    for (unsigned long _q = q; _q!=y; ) { const unsigned long h = _res[_q]; _res[_q] = y; _q = h; }
+                  }
+                }
+              }
+            }
+          }
+        }
+
+        // Resolve equivalences.
+        unsigned long counter = 0;
+        ptr = _res.data();
+        cimg_foroff(_res,p) { *ptr = *ptr==p?counter++:_res[*ptr]; ++ptr; }
+      }
+      return res;
+    }
+
+    //@}
+    //---------------------------------
+    //
+    //! \name Color Base Management
+    //@{
+    //---------------------------------
+
+    //! Return a palette 'default' with 256 RGB entries.
+    static const CImg<Tuchar>& default_LUT256() {
+      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 palette 'HSV' with 256 RGB entries.
+    static const CImg<Tuchar>& HSV_LUT256() {
+      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 palette 'lines' with 256 RGB entries.
+    static const CImg<Tuchar>& lines_LUT256() {
+      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;
+    }
+
+    //! Return the palette 'hot' with 256 RGB entries.
+    static const CImg<Tuchar>& hot_LUT256() {
+      static CImg<Tuchar> palette;
+      if (!palette) {
+        palette.assign(1,4,1,3,0);
+        palette[1] = palette[2] = palette[3] = palette[6] = palette[7] = palette[11] = 255;
+        palette.resize(1,256,1,3,3);
+      }
+      return palette;
+    }
+
+    //! Return the palette 'cool' with 256 RGB entries.
+    static const CImg<Tuchar>& cool_LUT256() {
+      static CImg<Tuchar> palette;
+      if (!palette) palette.assign(1,2,1,3).fill(0,255,255,0,255,255).resize(1,256,1,3,3);
+      return palette;
+    }
+
+    //! Return palette 'jet' with 256 RGB entries.
+    static const CImg<Tuchar>& jet_LUT256() {
+      static CImg<Tuchar> palette;
+      if (!palette) {
+        palette.assign(1,4,1,3,0);
+        palette[2] = palette[3] = palette[5] = palette[6] = palette[8] = palette[9] = 255;
+        palette.resize(1,256,1,3,3);
+      }
+      return palette;
+    }
+
+    //! Return palette 'flag' with 256 RGB entries.
+    static const CImg<Tuchar>& flag_LUT256() {
+      static CImg<Tuchar> palette;
+      if (!palette) {
+        palette.assign(1,4,1,3,0);
+        palette[0] = palette[1] = palette[5] = palette[9] = palette[10] = 255;
+        palette.resize(1,256,1,3,0,2);
+      }
+      return palette;
+    }
+
+    //! Return palette 'cube' with 256 RGB entries.
+    static const CImg<Tuchar>& cube_LUT256() {
+      static CImg<Tuchar> palette;
+      if (!palette) {
+        palette.assign(1,8,1,3,0);
+        palette[1] = palette[3] = palette[5] = palette[7] =
+          palette[10] = palette[11] = palette[12] = palette[13] =
+          palette[20] = palette[21] = palette[22] = palette[23] = 255;
+        palette.resize(1,256,1,3,3);
+      }
+      return palette;
+    }
+
+    //! Convert color pixels from sRGB to RGB.
+    CImg<T>& sRGBtoRGB() {
+      cimg_for(*this,ptr,T) {
+        const Tfloat
+          sval = (Tfloat)*ptr,
+          nsval = (sval<0?0:sval>255?255:sval)/255,
+          val = (Tfloat)(nsval<=0.04045f?nsval/12.92f:std::pow((nsval+0.055f)/(1.055f),2.4f));
+        *ptr = (T)(val*255);
+      }
+      return *this;
+    }
+
+    CImg<Tfloat> get_sRGBtoRGB() const {
+      return CImg<Tfloat>(*this,false).sRGBtoRGB();
+    }
+
+    //! Convert color pixels from RGB to sRGB.
+    CImg<T>& RGBtosRGB() {
+      cimg_for(*this,ptr,T) {
+        const Tfloat
+          val = (Tfloat)*ptr,
+          nval = (val<0?0:val>255?255:val)/255,
+          sval = (Tfloat)(nval<=0.0031308f?nval*12.92f:1.055f*std::pow(nval,0.416667f)-0.055f);
+        *ptr = (T)(sval*255);
+      }
+      return *this;
+    }
+
+    CImg<Tfloat> get_RGBtosRGB() const {
+      return CImg<Tfloat>(*this,false).RGBtosRGB();
+    }
+
+    //! Convert color pixels from RGB to HSV.
+    CImg<T>& RGBtoHSV() {
+      if (_spectrum!=3)
+        throw CImgInstanceException(_cimg_instance
+                                    "RGBtoHSV() : Instance is not a RGB image.",
+                                    cimg_instance);
+
+      T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2);
+      for (unsigned int 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 HSV to RGB.
+    CImg<T>& HSVtoRGB() {
+      if (_spectrum!=3)
+        throw CImgInstanceException(_cimg_instance
+                                    "HSVtoRGB() : Instance is not a HSV image.",
+                                    cimg_instance);
+
+      T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2);
+      for (unsigned int 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)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 RGB to HSL.
+    CImg<T>& RGBtoHSL() {
+      if (_spectrum!=3)
+        throw CImgInstanceException(_cimg_instance
+                                    "RGBtoHSL() : Instance is not a RGB image.",
+                                    cimg_instance);
+
+      T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2);
+      for (unsigned int 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 HSL to RGB.
+    CImg<T>& HSLtoRGB() {
+      if (_spectrum!=3)
+        throw CImgInstanceException(_cimg_instance
+                                    "HSLtoRGB() : Instance is not a HSL image.",
+                                    cimg_instance);
+
+      T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2);
+      for (unsigned int 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 RGB to HSI.
+    //! Reference: "Digital Image Processing, 2nd. edition", R. Gonzalez and R. Woods. Prentice Hall, 2002.
+    CImg<T>& RGBtoHSI() {
+      if (_spectrum!=3)
+        throw CImgInstanceException(_cimg_instance
+                                    "RGBtoHSI() : Instance is not a RGB image.",
+                                    cimg_instance);
+
+      T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2);
+      for (unsigned int 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)(std::acos(0.5f*((nR-nG)+(nR-nB))/std::sqrt(std::pow(nR-nG,2)+(nR-nB)*(nG-nB)))*180/cimg::PI),
+          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 HSI to RGB.
+    CImg<T>& HSItoRGB() {
+      if (_spectrum!=3)
+        throw CImgInstanceException(_cimg_instance
+                                    "HSItoRGB() : Instance is not a HSI image.",
+                                    cimg_instance);
+
+      T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2);
+      for (unsigned int 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*std::cos(H*cimg::PI/180)/std::cos((60-H)*cimg::PI/180)));
+          G = 3*I-(R+B);
+        } else if (H<240) {
+          H-=120;
+          R = a;
+          G = (Tfloat)(I*(1+S*std::cos(H*cimg::PI/180)/std::cos((60-H)*cimg::PI/180)));
+          B = 3*I-(R+G);
+        } else {
+          H-=240;
+          G = a;
+          B = (Tfloat)(I*(1+S*std::cos(H*cimg::PI/180)/std::cos((60-H)*cimg::PI/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 RGB to YCbCr.
+    CImg<T>& RGBtoYCbCr() {
+      if (_spectrum!=3)
+        throw CImgInstanceException(_cimg_instance
+                                    "RGBtoYCbCr() : Instance is not a RGB image.",
+                                    cimg_instance);
+
+      T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2);
+      for (unsigned int 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 RGB to YCbCr.
+    CImg<T>& YCbCrtoRGB() {
+      if (_spectrum!=3)
+        throw CImgInstanceException(_cimg_instance
+                                    "YCbCrtoRGB() : Instance is not a YCbCr image.",
+                                    cimg_instance);
+
+      T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2);
+      for (unsigned int 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 RGB to YUV.
+    CImg<T>& RGBtoYUV() {
+      if (_spectrum!=3)
+        throw CImgInstanceException(_cimg_instance
+                                    "RGBtoYUV() : Instance is not a RGB image.",
+                                    cimg_instance);
+
+      T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2);
+      for (unsigned int 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 YUV to RGB.
+    CImg<T>& YUVtoRGB() {
+      if (_spectrum!=3)
+        throw CImgInstanceException(_cimg_instance
+                                    "YUVtoRGB() : Instance is not a YUV image.",
+                                    cimg_instance);
+
+      T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2);
+      for (unsigned int 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 RGB to CMY.
+    CImg<T>& RGBtoCMY() {
+      if (_spectrum!=3)
+        throw CImgInstanceException(_cimg_instance
+                                    "RGBtoCMY() : Instance is not a RGB image.",
+                                    cimg_instance);
+
+      T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2);
+      for (unsigned int N = _width*_height*_depth; N; --N) {
+        const Tfloat
+          R = (Tfloat)*p1,
+          G = (Tfloat)*p2,
+          B = (Tfloat)*p3,
+          C = 255 - R,
+          M = 255 - G,
+          Y = 255 - B;
+        *(p1++) = (T)(C<0?0:(C>255?255:C));
+        *(p2++) = (T)(M<0?0:(M>255?255:M));
+        *(p3++) = (T)(Y<0?0:(Y>255?255:Y));
+      }
+      return *this;
+    }
+
+    CImg<Tuchar> get_RGBtoCMY() const {
+      return CImg<Tfloat>(*this,false).RGBtoCMY();
+    }
+
+    //! Convert CMY pixels of a color image into the RGB color space.
+    CImg<T>& CMYtoRGB() {
+      if (_spectrum!=3)
+        throw CImgInstanceException(_cimg_instance
+                                    "CMYtoRGB() : Instance is not a CMY image.",
+                                    cimg_instance);
+
+      T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2);
+      for (unsigned int N = _width*_height*_depth; N; --N) {
+        const Tfloat
+          C = (Tfloat)*p1,
+          M = (Tfloat)*p2,
+          Y = (Tfloat)*p3,
+          R = 255 - C,
+          G = 255 - M,
+          B = 255 - 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 CMY to CMYK.
+    CImg<T>& CMYtoCMYK() {
+      return get_CMYtoCMYK().move_to(*this);
+    }
+
+    CImg<Tuchar> get_CMYtoCMYK() const {
+      if (_spectrum!=3)
+        throw CImgInstanceException(_cimg_instance
+                                    "CMYtoCMYK() : Instance is not a CMY image.",
+                                    cimg_instance);
+
+      CImg<Tfloat> res(_width,_height,_depth,4);
+      const T *ps1 = data(0,0,0,0), *ps2 = data(0,0,0,1), *ps3 = data(0,0,0,2);
+      Tfloat *pd1 = res.data(0,0,0,0), *pd2 = res.data(0,0,0,1), *pd3 = res.data(0,0,0,2), *pd4 = res.data(0,0,0,3);
+      for (unsigned int N = _width*_height*_depth; N; --N) {
+        Tfloat
+          C = (Tfloat)*(ps1++),
+          M = (Tfloat)*(ps2++),
+          Y = (Tfloat)*(ps3++),
+          K = cimg::min(C,M,Y);
+        if (K>=255) C = M = Y = 0;
+        else { const Tfloat K1 = 255 - K; C = 255*(C - K)/K1; M = 255*(M - K)/K1; Y = 255*(Y - K)/K1; }
+        *(pd1++) = (Tfloat)(C<0?0:(C>255?255:C));
+        *(pd2++) = (Tfloat)(M<0?0:(M>255?255:M));
+        *(pd3++) = (Tfloat)(Y<0?0:(Y>255?255:Y));
+        *(pd4++) = (Tfloat)(K<0?0:(K>255?255:K));
+      }
+      return res;
+    }
+
+    //! Convert CMYK pixels of a color image into the CMY color space.
+    CImg<T>& CMYKtoCMY() {
+      return get_CMYKtoCMY().move_to(*this);
+    }
+
+    CImg<Tfloat> get_CMYKtoCMY() const {
+      if (_spectrum!=4)
+        throw CImgInstanceException(_cimg_instance
+                                    "CMYKtoCMY() : Instance is not a CMYK image.",
+                                    cimg_instance);
+
+      CImg<Tfloat> res(_width,_height,_depth,3);
+      const T *ps1 = data(0,0,0,0), *ps2 = data(0,0,0,1), *ps3 = data(0,0,0,2), *ps4 = data(0,0,0,3);
+      Tfloat *pd1 = res.data(0,0,0,0), *pd2 = res.data(0,0,0,1), *pd3 = res.data(0,0,0,2);
+      for (unsigned int N = _width*_height*_depth; N; --N) {
+        const Tfloat
+          C = (Tfloat)*(ps1++),
+          M = (Tfloat)*(ps2++),
+          Y = (Tfloat)*(ps3++),
+          K = (Tfloat)*(ps4++),
+          K1 = 1 - K/255,
+          nC = C*K1 + K,
+          nM = M*K1 + K,
+          nY = Y*K1 + K;
+        *(pd1++) = (Tfloat)(nC<0?0:(nC>255?255:nC));
+        *(pd2++) = (Tfloat)(nM<0?0:(nM>255?255:nM));
+        *(pd3++) = (Tfloat)(nY<0?0:(nY>255?255:nY));
+      }
+      return res;
+    }
+
+    //! Convert color pixels from RGB to XYZ_709.
+    CImg<T>& RGBtoXYZ() {
+      if (_spectrum!=3)
+        throw CImgInstanceException(_cimg_instance
+                                    "RGBtoXYZ() : Instance is not a RGB image.",
+                                    cimg_instance);
+
+      T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2);
+      for (unsigned int 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 XYZ_709 pixels of a color image into the RGB color space.
+    CImg<T>& XYZtoRGB() {
+      if (_spectrum!=3)
+        throw CImgInstanceException(_cimg_instance
+                                    "XYZtoRGB() : Instance is not a XYZ image.",
+                                    cimg_instance);
+
+      T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2);
+      for (unsigned int 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 XYZ_709 pixels of a color image into the (L*,a*,b*) color space.
+    CImg<T>& XYZtoLab() {
+#define _cimg_Labf(x) ((x)>=0.008856f?(std::pow(x,(Tfloat)1/3)):(7.787f*(x)+16.0f/116))
+
+      if (_spectrum!=3)
+        throw CImgInstanceException(_cimg_instance
+                                    "XYZtoLab() : Instance is not a XYZ image.",
+                                    cimg_instance);
+
+      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 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2);
+      for (unsigned int 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 Lab pixels of a color image into the XYZ color space.
+    CImg<T>& LabtoXYZ() {
+#define _cimg_Labfi(x) ((x)>=0.206893f?((x)*(x)*(x)):(((x)-16.0f/116)/7.787f))
+
+      if (_spectrum!=3)
+        throw CImgInstanceException(_cimg_instance
+                                    "LabtoXYZ() : Instance is not a Lab image.",
+                                    cimg_instance);
+
+      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 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2);
+      for (unsigned int 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)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 XYZ_709 pixels of a color image into the xyY color space.
+    CImg<T>& XYZtoxyY() {
+      if (_spectrum!=3)
+        throw CImgInstanceException(_cimg_instance
+                                    "XYZtoxyY() : Instance is not a XYZ image.",
+                                    cimg_instance);
+
+      T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2);
+      for (unsigned int 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 xyY pixels of a color image into the XYZ_709 color space.
+    CImg<T>& xyYtoXYZ() {
+      if (_spectrum!=3)
+        throw CImgInstanceException(_cimg_instance
+                                    "xyYtoXYZ() : Instance is not a xyY image.",
+                                    cimg_instance);
+
+      T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2);
+      for (unsigned int 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 RGB image to a Lab one.
+    CImg<T>& RGBtoLab() {
+      return RGBtoXYZ().XYZtoLab();
+    }
+
+    CImg<Tfloat> get_RGBtoLab() const {
+      return CImg<Tfloat>(*this,false).RGBtoLab();
+    }
+
+    //! Convert a Lab image to a RGB one.
+    CImg<T>& LabtoRGB() {
+      return LabtoXYZ().XYZtoRGB();
+    }
+
+    CImg<Tuchar> get_LabtoRGB() const {
+      return CImg<Tuchar>(*this,false).LabtoRGB();
+    }
+
+    //! Convert a RGB image to a xyY one.
+    CImg<T>& RGBtoxyY() {
+      return RGBtoXYZ().XYZtoxyY();
+    }
+
+    CImg<Tfloat> get_RGBtoxyY() const {
+      return CImg<Tfloat>(*this,false).RGBtoxyY();
+    }
+
+    //! Convert a xyY image to a RGB one.
+    CImg<T>& xyYtoRGB() {
+      return xyYtoXYZ().XYZtoRGB();
+    }
+
+    CImg<Tuchar> get_xyYtoRGB() const {
+      return CImg<Tuchar>(*this,false).xyYtoRGB();
+    }
+
+    //! Convert a RGB image to a CMYK one.
+    CImg<T>& RGBtoCMYK() {
+      return RGBtoCMY().CMYtoCMYK();
+    }
+
+    CImg<Tfloat> get_RGBtoCMYK() const {
+      return CImg<Tfloat>(*this,false).RGBtoCMYK();
+    }
+
+    //! Convert a CMYK image to a RGB one.
+    CImg<T>& CMYKtoRGB() {
+      return CMYKtoCMY().CMYtoRGB();
+    }
+
+    CImg<Tuchar> get_CMYKtoRGB() const {
+      return CImg<Tuchar>(*this,false).CMYKtoRGB();
+    }
+
+    //! Convert a RGB 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().move_to(*this);
+    }
+
+    CImg<T> get_RGBtoBayer() const {
+      if (_spectrum!=3)
+        throw CImgInstanceException(_cimg_instance
+                                    "RGBtoBayer() : Instance is not a RGB image.",
+                                    cimg_instance);
+
+      CImg<T> res(_width,_height,_depth,1);
+      const T *ptr_r = data(0,0,0,0), *ptr_g = data(0,0,0,1), *ptr_b = data(0,0,0,2);
+      T *ptrd = res._data;
+      cimg_forXYZ(*this,x,y,z) {
+        if (y%2) {
+          if (x%2) *(ptrd++) = *ptr_b;
+          else *(ptrd++) = *ptr_g;
+        } else {
+          if (x%2) *(ptrd++) = *ptr_g;
+          else *(ptrd++) = *ptr_r;
+        }
+        ++ptr_r; ++ptr_g; ++ptr_b;
+      }
+      return res;
+    }
+
+    //! Convert a Bayer-coded image to a RGB color image.
+    CImg<T>& BayertoRGB(const unsigned int interpolation_type=3) {
+      return get_BayertoRGB(interpolation_type).move_to(*this);
+    }
+
+    CImg<Tuchar> get_BayertoRGB(const unsigned int interpolation_type=3) const {
+      if (_spectrum!=1)
+        throw CImgInstanceException(_cimg_instance
+                                    "BayertoRGB() : Instance is not a Bayer image.",
+                                    cimg_instance);
+
+      CImg<Tuchar> res(_width,_height,_depth,3);
+      CImg_3x3(I,T);
+      Tuchar *ptr_r = res.data(0,0,0,0), *ptr_g = res.data(0,0,0,1), *ptr_b = res.data(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<width()-1?x+1:x-1, _n1y = y<height()-1?y+1:y-1;
+          cimg_get3x3(*this,x,y,z,0,I,T);
+          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);
+              *ptr_g = (Tuchar)((cx*(Inc+Ipc) + cy*(Icn+Icp))/(2*(cx+cy)));
+            } else *ptr_g = (Tuchar)Icc;
+          } else {
+            if (x%2) *ptr_g = (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);
+              *ptr_g = (Tuchar)((cx*(Inc+Ipc) + cy*(Icn+Icp))/(2*(cx+cy)));
+            }
+          }
+          ++ptr_g;
+        }
+        cimg_forXYZ(*this,x,y,z) {
+          const int _p1x = x?x-1:1, _p1y = y?y-1:1, _n1x = x<width()-1?x+1:x-1, _n1y = y<height()-1?y+1:y-1;
+          cimg_get3x3(*this,x,y,z,0,I,T);
+          cimg_get3x3(res,x,y,z,1,G,T);
+          if (y%2) {
+            if (x%2) *ptr_b = (Tuchar)Icc;
+            else { *ptr_r = (Tuchar)((Icn+Icp)/2); *ptr_b = (Tuchar)((Inc+Ipc)/2); }
+          } else {
+            if (x%2) { *ptr_r = (Tuchar)((Inc+Ipc)/2); *ptr_b = (Tuchar)((Icn+Icp)/2); }
+            else *ptr_r = (Tuchar)Icc;
+          }
+          ++ptr_r; ++ptr_b;
+        }
+        ptr_r = res.data(0,0,0,0);
+        ptr_g = res.data(0,0,0,1);
+        ptr_b = res.data(0,0,0,2);
+        cimg_forXYZ(*this,x,y,z) {
+          const int _p1x = x?x-1:1, _p1y = y?y-1:1, _n1x = x<width()-1?x+1:x-1, _n1y = y<height()-1?y+1:y-1;
+          cimg_get3x3(res,x,y,z,0,R,T);
+          cimg_get3x3(res,x,y,z,1,G,T);
+          cimg_get3x3(res,x,y,z,2,B,T);
+          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);
+              *ptr_r = (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);
+              *ptr_b = (Tuchar)((cx*(Bnc+Bpc) + cy*(Bcn+Bcp))/(2*(cx+cy)));
+            }
+          }
+          ++ptr_r; ++ptr_g; ++ptr_b;
+        }
+      } 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<width()-1?x+1:x-1, _n1y = y<height()-1?y+1:y-1;
+          cimg_get3x3(*this,x,y,z,0,I,T);
+          if (y%2) {
+            if (x%2) { *ptr_r = (Tuchar)((Ipp+Inn+Ipn+Inp)/4); *ptr_g = (Tuchar)((Inc+Ipc+Icn+Icp)/4); *ptr_b = (Tuchar)Icc; }
+            else { *ptr_r = (Tuchar)((Icp+Icn)/2); *ptr_g = (Tuchar)Icc; *ptr_b = (Tuchar)((Inc+Ipc)/2); }
+          } else {
+            if (x%2) { *ptr_r = (Tuchar)((Ipc+Inc)/2); *ptr_g = (Tuchar)Icc; *ptr_b = (Tuchar)((Icn+Icp)/2); }
+            else { *ptr_r = (Tuchar)Icc; *ptr_g = (Tuchar)((Inc+Ipc+Icn+Icp)/4); *ptr_b = (Tuchar)((Ipp+Inn+Ipn+Inp)/4); }
+          }
+          ++ptr_r; ++ptr_g; ++ptr_b;
+        }
+      } 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<width()-1?x+1:x-1, _n1y = y<height()-1?y+1:y-1;
+          cimg_get3x3(*this,x,y,z,0,I,T);
+          if (y%2) {
+            if (x%2) { *ptr_r = (Tuchar)cimg::min(Ipp,Inn,Ipn,Inp); *ptr_g = (Tuchar)cimg::min(Inc,Ipc,Icn,Icp); *ptr_b = (Tuchar)Icc; }
+            else { *ptr_r = (Tuchar)cimg::min(Icn,Icp); *ptr_g = (Tuchar)Icc; *ptr_b = (Tuchar)cimg::min(Inc,Ipc); }
+          } else {
+            if (x%2) { *ptr_r = (Tuchar)cimg::min(Inc,Ipc); *ptr_g = (Tuchar)Icc; *ptr_b = (Tuchar)cimg::min(Icn,Icp); }
+            else { *ptr_r = (Tuchar)Icc; *ptr_g = (Tuchar)cimg::min(Inc,Ipc,Icn,Icp); *ptr_b = (Tuchar)cimg::min(Ipp,Inn,Ipn,Inp); }
+          }
+          ++ptr_r; ++ptr_g; ++ptr_b;
+        }
+      } 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) *ptr_b = val; else *ptr_g = val; } else { if (x%2) *ptr_g = val; else *ptr_r = val; }
+          ++ptr_r; ++ptr_g; ++ptr_b;
+        }
+      }
+      }
+      return res;
+    }
+
+    //@}
+    //------------------------------------------
+    //
+    //! \name Geometric / Spatial Manipulation
+    //@{
+    //------------------------------------------
+
+    static float _cimg_lanczos(const float x) {
+      if (x<=-2 || x>=2) return 0;
+      const float a = (float)cimg::PI*x, b = 0.5f*a;
+      return (float)(x?std::sin(a)*std::sin(b)/(a*b):1);
+    }
+
+    //! Resize an image.
+    /**
+       \param size_x Number of columns (new size along the X-axis).
+       \param size_y Number of rows (new size along the Y-axis).
+       \param size_z Number of slices (new size along the Z-axis).
+       \param size_c Number of vector-channels (new size along the C-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 = nearest-neighbor interpolation.
+       - 2 = moving average interpolation.
+       - 3 = linear interpolation.
+       - 4 = grid interpolation.
+       - 5 = bicubic interpolation.
+       - 6 = lanczos interpolation.
+       \param border_conditions Border condition type.
+       \param centering_x Set centering type (only if \p interpolation_type=0).
+       \param centering_y Set centering type (only if \p interpolation_type=0).
+       \param centering_z Set centering type (only if \p interpolation_type=0).
+       \param centering_c 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 size_x, const int size_y=-100,
+                    const int size_z=-100, const int size_c=-100,
+                    const int interpolation_type=1, const unsigned int border_conditions=0,
+                    const float centering_x = 0, const float centering_y = 0,
+                    const float centering_z = 0, const float centering_c = 0) {
+      if (!size_x || !size_y || !size_z || !size_c) return assign();
+      const unsigned int
+        _sx = (unsigned int)(size_x<0?-size_x*width()/100:size_x),
+        _sy = (unsigned int)(size_y<0?-size_y*height()/100:size_y),
+        _sz = (unsigned int)(size_z<0?-size_z*depth()/100:size_z),
+        _sc = (unsigned int)(size_c<0?-size_c*spectrum()/100:size_c),
+        sx = _sx?_sx:1, sy = _sy?_sy:1, sz = _sz?_sz:1, sc = _sc?_sc:1;
+      if (sx==_width && sy==_height && sz==_depth && sc==_spectrum) return *this;
+      if (is_empty()) return assign(sx,sy,sz,sc,0);
+      if (interpolation_type==-1 && sx*sy*sz*sc==size()) {
+        _width = sx; _height = sy; _depth = sz; _spectrum = sc;
+        return *this;
+      }
+      return get_resize(sx,sy,sz,sc,interpolation_type,border_conditions,centering_x,centering_y,centering_z,centering_c).move_to(*this);
+    }
+
+    CImg<T> get_resize(const int size_x, const int size_y = -100,
+                       const int size_z = -100, const int size_c = -100,
+                       const int interpolation_type=1, const unsigned int border_conditions=0,
+                       const float centering_x = 0, const float centering_y = 0,
+                       const float centering_z = 0, const float centering_c = 0) const {
+      if (centering_x<0 || centering_x>1 || centering_y<0 || centering_y>1 ||
+          centering_z<0 || centering_z>1 || centering_c<0 || centering_c>1)
+        throw CImgArgumentException(_cimg_instance
+                                    "resize() : Specified centering arguments (%g,%g,%g,%g) are outside range [0,1].",
+                                    cimg_instance,
+                                    centering_x,centering_y,centering_z,centering_c);
+
+      if (!size_x || !size_y || !size_z || !size_c) return CImg<T>();
+      const unsigned int
+        _sx = (unsigned int)(size_x<0?-size_x*width()/100:size_x),
+        _sy = (unsigned int)(size_y<0?-size_y*height()/100:size_y),
+        _sz = (unsigned int)(size_z<0?-size_z*depth()/100:size_z),
+        _sc = (unsigned int)(size_c<0?-size_c*spectrum()/100:size_c),
+        sx = _sx?_sx:1, sy = _sy?_sy:1, sz = _sz?_sz:1, sc = _sc?_sc:1;
+      if (sx==_width && sy==_height && sz==_depth && sc==_spectrum) return +*this;
+      if (is_empty()) return CImg<T>(sx,sy,sz,sc,0);
+
+      CImg<T> res;
+      switch (interpolation_type) {
+
+        // Raw resizing.
+        //
+      case -1 :
+        std::memcpy(res.assign(sx,sy,sz,sc,0)._data,_data,sizeof(T)*cimg::min(size(),sx*sy*sz*sc));
+        break;
+
+        // No interpolation.
+        //
+      case 0 : {
+        const int
+          xc = (int)(centering_x*((int)sx - width())),
+          yc = (int)(centering_y*((int)sy - height())),
+          zc = (int)(centering_z*((int)sz - depth())),
+          cc = (int)(centering_c*((int)sc - spectrum()));
+
+        switch (border_conditions) {
+        case 2 : { // Cyclic borders.
+          res.assign(sx,sy,sz,sc);
+          const int
+            x0 = ((int)xc%width()) - width(),
+            y0 = ((int)yc%height()) - height(),
+            z0 = ((int)zc%depth()) - depth(),
+            c0 = ((int)cc%spectrum()) - spectrum();
+          for (int c = c0; c<(int)sc; c+=spectrum())
+            for (int z = z0; z<(int)sz; z+=depth())
+              for (int y = y0; y<(int)sy; y+=height())
+                for (int x = x0; x<(int)sx; x+=width())
+                  res.draw_image(x,y,z,c,*this);
+        } break;
+        case 1 : { // Neumann borders.
+          res.assign(sx,sy,sz,sc).draw_image(xc,yc,zc,cc,*this);
+          CImg<T> sprite;
+          if (xc>0) {  // X-backward
+            res.get_crop(xc,yc,zc,cc,xc,yc+height()-1,zc+depth()-1,cc+spectrum()-1).move_to(sprite);
+            for (int x = xc-1; x>=0; --x) res.draw_image(x,yc,zc,cc,sprite);
+          }
+          if (xc+width()<(int)sx) { // X-forward
+            res.get_crop(xc+width()-1,yc,zc,cc,xc+width()-1,yc+height()-1,zc+depth()-1,cc+spectrum()-1).move_to(sprite);
+            for (int x = xc+width(); x<(int)sx; ++x) res.draw_image(x,yc,zc,cc,sprite);
+          }
+          if (yc>0) {  // Y-backward
+            res.get_crop(0,yc,zc,cc,sx-1,yc,zc+depth()-1,cc+spectrum()-1).move_to(sprite);
+            for (int y = yc-1; y>=0; --y) res.draw_image(0,y,zc,cc,sprite);
+          }
+          if (yc+height()<(int)sy) { // Y-forward
+            res.get_crop(0,yc+height()-1,zc,cc,sx-1,yc+height()-1,zc+depth()-1,cc+spectrum()-1).move_to(sprite);
+            for (int y = yc+height(); y<(int)sy; ++y) res.draw_image(0,y,zc,cc,sprite);
+          }
+          if (zc>0) {  // Z-backward
+            res.get_crop(0,0,zc,cc,sx-1,sy-1,zc,cc+spectrum()-1).move_to(sprite);
+            for (int z = zc-1; z>=0; --z) res.draw_image(0,0,z,cc,sprite);
+          }
+          if (zc+depth()<(int)sz) { // Z-forward
+            res.get_crop(0,0,zc+depth()-1,cc,sx-1,sy-1,zc+depth()-1,cc+spectrum()-1).move_to(sprite);
+            for (int z = zc+depth(); z<(int)sz; ++z) res.draw_image(0,0,z,cc,sprite);
+          }
+          if (cc>0) {  // C-backward
+            res.get_crop(0,0,0,cc,sx-1,sy-1,sz-1,cc).move_to(sprite);
+            for (int c = cc-1; c>=0; --c) res.draw_image(0,0,0,c,sprite);
+          }
+          if (cc+spectrum()<(int)sc) { // C-forward
+            res.get_crop(0,0,0,cc+spectrum()-1,sx-1,sy-1,sz-1,cc+spectrum()-1).move_to(sprite);
+            for (int c = cc+spectrum(); c<(int)sc; ++c) res.draw_image(0,0,0,c,sprite);
+          }
+        } break;
+        default : // Dirichlet borders.
+          res.assign(sx,sy,sz,sc,0).draw_image(xc,yc,zc,cc,*this);
+        }
+        break;
+      } break;
+
+        // Nearest neighbor interpolation.
+        //
+      case 1 : {
+        res.assign(sx,sy,sz,sc);
+        CImg<ulongT> off_x(sx), off_y(sy+1), off_z(sz+1), off_c(sc+1);
+        unsigned long *poff_x, *poff_y, *poff_z, *poff_c, curr, old;
+        const unsigned long
+          wh = (unsigned long)_width*_height,
+          whd = (unsigned long)_width*_height*_depth,
+          sxy = (unsigned long)sx*sy,
+          sxyz = (unsigned long)sx*sy*sz;
+        if (sx==_width) off_x.fill(1);
+        else {
+          poff_x = off_x._data; curr = 0;
+          cimg_forX(res,x) { old = curr; curr = ((x+1LU)*_width/sx); *(poff_x++) = curr - old; }
+        }
+        if (sy==_height) off_y.fill(_width);
+        else {
+          poff_y = off_y._data; curr = 0;
+          cimg_forY(res,y) { old = curr; curr = ((y+1LU)*_height/sy); *(poff_y++) = _width*(curr - old); } *poff_y = 0;
+        }
+        if (sz==_depth) off_z.fill(wh);
+        else {
+          poff_z = off_z._data; curr = 0;
+          cimg_forZ(res,z) { old = curr; curr = ((z+1LU)*_depth/sz); *(poff_z++) = wh*(curr - old); } *poff_z = 0;
+        }
+        if (sc==_spectrum) off_c.fill(whd);
+        else {
+          poff_c = off_c._data; curr = 0;
+          cimg_forC(res,c) { old = curr; curr = ((c+1LU)*_spectrum/sc); *(poff_c++) = whd*(curr - old); } *poff_c = 0;
+        }
+
+        T *ptrd = res._data;
+        const T* ptrv = _data;
+        poff_c = off_c._data;
+        for (unsigned int c = 0; c<sc; ) {
+          const T *ptrz = ptrv;
+          poff_z = off_z._data;
+          for (unsigned int z = 0; z<sz; ) {
+            const T *ptry = ptrz;
+            poff_y = off_y._data;
+            for (unsigned int y = 0; y<sy; ) {
+              const T *ptrx = ptry;
+              poff_x = off_x._data;
+              cimg_forX(res,x) { *(ptrd++) = *ptrx; ptrx+=*(poff_x++); }
+              ++y;
+              unsigned long dy = *(poff_y++);
+              for (;!dy && y<dy; std::memcpy(ptrd,ptrd - sx,sizeof(T)*sx), ++y, ptrd+=sx, dy = *(poff_y++)) {}
+              ptry+=dy;
+            }
+            ++z;
+            unsigned long dz = *(poff_z++);
+            for (;!dz && z<dz; std::memcpy(ptrd,ptrd-sxy,sizeof(T)*sxy), ++z, ptrd+=sxy, dz = *(poff_z++)) {}
+            ptrz+=dz;
+          }
+          ++c;
+          unsigned long dc = *(poff_c++);
+          for (;!dc && c<dc; std::memcpy(ptrd,ptrd-sxyz,sizeof(T)*sxyz), ++c, ptrd+=sxyz, dc = *(poff_c++)) {}
+          ptrv+=dc;
+        }
+      } break;
+
+        // Moving average.
+        //
+      case 2 : {
+        bool instance_first = true;
+        if (sx!=_width) {
+          CImg<Tfloat> tmp(sx,_height,_depth,_spectrum,0);
+          for (unsigned int a = _width*sx, b = _width, c = sx, s = 0, t = 0; a; ) {
+            const unsigned int d = cimg::min(b,c);
+            a-=d; b-=d; c-=d;
+            cimg_forYZC(tmp,y,z,v) tmp(t,y,z,v)+=(Tfloat)(*this)(s,y,z,v)*d;
+            if (!b) { cimg_forYZC(tmp,y,z,v) tmp(t,y,z,v)/=_width; ++t; b = _width; }
+            if (!c) { ++s; c = sx; }
+          }
+          tmp.move_to(res);
+          instance_first = false;
+        }
+        if (sy!=_height) {
+          CImg<Tfloat> tmp(sx,sy,_depth,_spectrum,0);
+          for (unsigned int a = _height*sy, b = _height, c = sy, s = 0, t = 0; a; ) {
+            const unsigned int d = cimg::min(b,c);
+            a-=d; b-=d; c-=d;
+            if (instance_first) cimg_forXZC(tmp,x,z,v) tmp(x,t,z,v)+=(Tfloat)(*this)(x,s,z,v)*d;
+            else cimg_forXZC(tmp,x,z,v) tmp(x,t,z,v)+=(Tfloat)res(x,s,z,v)*d;
+            if (!b) { cimg_forXZC(tmp,x,z,v) tmp(x,t,z,v)/=_height; ++t; b = _height; }
+            if (!c) { ++s; c = sy; }
+          }
+          tmp.move_to(res);
+          instance_first = false;
+        }
+        if (sz!=_depth) {
+          CImg<Tfloat> tmp(sx,sy,sz,_spectrum,0);
+          for (unsigned int a = _depth*sz, b = _depth, c = sz, s = 0, t = 0; a; ) {
+            const unsigned int d = cimg::min(b,c);
+            a-=d; b-=d; c-=d;
+            if (instance_first) cimg_forXYC(tmp,x,y,v) tmp(x,y,t,v)+=(Tfloat)(*this)(x,y,s,v)*d;
+            else cimg_forXYC(tmp,x,y,v) tmp(x,y,t,v)+=(Tfloat)res(x,y,s,v)*d;
+            if (!b) { cimg_forXYC(tmp,x,y,v) tmp(x,y,t,v)/=_depth; ++t; b = _depth; }
+            if (!c) { ++s; c = sz; }
+          }
+          tmp.move_to(res);
+          instance_first = false;
+        }
+        if (sc!=_spectrum) {
+          CImg<Tfloat> tmp(sx,sy,sz,sc,0);
+          for (unsigned int a = _spectrum*sc, b = _spectrum, c = sc, 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)/=_spectrum; ++t; b = _spectrum; }
+            if (!c) { ++s; c = sc; }
+          }
+          tmp.move_to(res);
+          instance_first = false;
+        }
+      } break;
+
+        // Linear interpolation.
+        //
+      case 3 : {
+        CImg<uintT> off(cimg::max(sx,sy,sz,sc));
+        CImg<floatT> foff(off._width);
+        unsigned int *poff;
+        float *pfoff, old, curr;
+        CImg<T> resx, resy, resz, resc;
+        T *ptrd;
+
+        if (sx!=_width) {
+          if (_width==1) get_resize(sx,_height,_depth,_spectrum,1).move_to(resx);
+          else {
+            const float fx = (!border_conditions && sx>_width)?(sx>1?(_width-1.0f)/(sx-1):0):(float)_width/sx;
+            resx.assign(sx,_height,_depth,_spectrum);
+            curr = old = 0; poff = off._data; pfoff = foff._data;
+            cimg_forX(resx,x) { *(pfoff++) = curr - (unsigned int)curr; old = curr; curr+=fx; *(poff++) = (unsigned int)curr - (unsigned int)old; }
+            ptrd = resx._data;
+            const T *ptrs0 = _data;
+            cimg_forYZC(resx,y,z,c) {
+              poff = off._data; pfoff = foff._data;
+              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):val1;
+                *(ptrd++) = (T)((1-alpha)*val1 + alpha*val2);
+                ptrs+=*(poff++);
+              }
+              ptrs0+=_width;
+            }
+          }
+        } else resx.assign(*this,true);
+
+        if (sy!=_height) {
+          if (_height==1) resx.get_resize(sx,sy,_depth,_spectrum,1).move_to(resy);
+          else {
+            const float fy = (!border_conditions && sy>_height)?(sy>1?(_height-1.0f)/(sy-1):0):(float)_height/sy;
+            resy.assign(sx,sy,_depth,_spectrum);
+            curr = old = 0; poff = off._data; pfoff = foff._data;
+            cimg_forY(resy,y) { *(pfoff++) = curr - (unsigned int)curr; old = curr; curr+=fy; *(poff++) = sx*((unsigned int)curr-(unsigned int)old); }
+            cimg_forXZC(resy,x,z,c) {
+              ptrd = resy.data(x,0,z,c);
+              const T *ptrs = resx.data(x,0,z,c), *const ptrsmax = ptrs + (_height-1)*sx;
+              poff = off._data; pfoff = foff._data;
+              cimg_forY(resy,y) {
+                const float alpha = *(pfoff++);
+                const T val1 = *ptrs, val2 = ptrs<ptrsmax?*(ptrs+sx):val1;
+                *ptrd = (T)((1-alpha)*val1 + alpha*val2);
+                ptrd+=sx;
+                ptrs+=*(poff++);
+              }
+            }
+          }
+          resx.assign();
+        } else resy.assign(resx,true);
+
+        if (sz!=_depth) {
+          if (_depth==1) resy.get_resize(sx,sy,sz,_spectrum,1).move_to(resz);
+          else {
+            const float fz = (!border_conditions && sz>_depth)?(sz>1?(_depth-1.0f)/(sz-1):0):(float)_depth/sz;
+            const unsigned int sxy = sx*sy;
+            resz.assign(sx,sy,sz,_spectrum);
+            curr = old = 0; poff = off._data; pfoff = foff._data;
+            cimg_forZ(resz,z) { *(pfoff++) = curr - (unsigned int)curr; old = curr; curr+=fz; *(poff++) = sxy*((unsigned int)curr - (unsigned int)old); }
+            cimg_forXYC(resz,x,y,c) {
+              ptrd = resz.data(x,y,0,c);
+              const T *ptrs = resy.data(x,y,0,c), *const ptrsmax = ptrs + (_depth-1)*sxy;
+              poff = off._data; pfoff = foff._data;
+              cimg_forZ(resz,z) {
+                const float alpha = *(pfoff++);
+                const T val1 = *ptrs, val2 = ptrs<ptrsmax?*(ptrs+sxy):val1;
+                *ptrd = (T)((1-alpha)*val1 + alpha*val2);
+                ptrd+=sxy;
+                ptrs+=*(poff++);
+              }
+            }
+          }
+          resy.assign();
+        } else resz.assign(resy,true);
+
+        if (sc!=_spectrum) {
+          if (_spectrum==1) resz.get_resize(sx,sy,sz,sc,1).move_to(resc);
+          else {
+            const float fc = (!border_conditions && sc>_spectrum)?(sc>1?(_spectrum-1.0f)/(sc-1):0):(float)_spectrum/sc;
+            const unsigned int sxyz = sx*sy*sz;
+            resc.assign(sx,sy,sz,sc);
+            curr = old = 0; poff = off._data; pfoff = foff._data;
+            cimg_forC(resc,c) { *(pfoff++) = curr - (unsigned int)curr; old = curr; curr+=fc; *(poff++) = sxyz*((unsigned int)curr - (unsigned int)old); }
+            cimg_forXYZ(resc,x,y,z) {
+              ptrd = resc.data(x,y,z,0);
+              const T *ptrs = resz.data(x,y,z,0), *const ptrsmax = ptrs + (_spectrum-1)*sxyz;
+              poff = off._data; pfoff = foff._data;
+              cimg_forC(resc,c) {
+                const float alpha = *(pfoff++);
+                const T val1 = *ptrs, val2 = ptrs<ptrsmax?*(ptrs+sxyz):val1;
+                *ptrd = (T)((1-alpha)*val1 + alpha*val2);
+                ptrd+=sxyz;
+                ptrs+=*(poff++);
+              }
+            }
+          }
+          resz.assign();
+        } else resc.assign(resz,true);
+        return resc._is_shared?(resz._is_shared?(resy._is_shared?(resx._is_shared?(+(*this)):resx):resy):resz):resc;
+      } break;
+
+        // Grid interpolation.
+        //
+      case 4 : {
+        CImg<T> resx, resy, resz, resc;
+        const unsigned int sxy = sx*sy, sxyz = sx*sy*sz;
+        if (sx!=_width) {
+          if (sx<_width) get_resize(sx,_height,_depth,_spectrum,1).move_to(resx);
+          else {
+            resx.assign(sx,_height,_depth,_spectrum,0);
+            const T *ptrs = _data;
+            T *ptrd = resx._data + (int)(centering_x*(sx-1)/_width);
+            cimg_forYZC(*this,y,z,c) {
+              cimg_forX(*this,x) ptrd[x*sx/_width] = *(ptrs++);
+              ptrd+=sx;
+            }
+          }
+        } else resx.assign(*this,true);
+
+        if (sy!=_height) {
+          if (sy<_height) resx.get_resize(sx,sy,_depth,_spectrum,1).move_to(resy);
+          else {
+            resy.assign(sx,sy,_depth,_spectrum,0);
+            const T *ptrs = resx._data;
+            T *ptrd = resy._data + (int)(centering_y*(sy-1)/_height)*sx;
+            cimg_forZC(*this,z,c) {
+              cimg_forY(*this,y) { std::memcpy(ptrd + (y*sy/_height)*sx,ptrs,sizeof(T)*sx); ptrs+=sx; }
+              ptrd+=sxy;
+            }
+          }
+          resx.assign();
+        } else resy.assign(resx,true);
+
+        if (sz!=_depth) {
+          if (sz<_depth) resy.get_resize(sx,sy,sz,_spectrum,1).move_to(resz);
+          else {
+            resz.assign(sx,sy,sz,_spectrum,0);
+            const T *ptrs = resy._data;
+            T *ptrd = resz._data + (int)(centering_z*(sz-1)/_depth)*sxy;
+            cimg_forC(*this,c) {
+              cimg_forZ(*this,z) { std::memcpy(ptrd + (z*sz/_depth)*sxy,ptrs,sizeof(T)*sxy); ptrs+=sxy; }
+              ptrd+=sxyz;
+            }
+          }
+          resy.assign();
+        } else resz.assign(resy,true);
+
+        if (sc!=_spectrum) {
+          if (sc<_spectrum) resz.get_resize(sx,sy,sz,sc,1).move_to(resc);
+          else {
+            resc.assign(sx,sy,sz,sc,0);
+            const T *ptrs = resz._data;
+            T *ptrd = resc._data + (int)(centering_c*(sc-1)/_spectrum)*sxyz;
+            cimg_forC(*this,c) { std::memcpy(ptrd + (c*sc/_spectrum)*sxyz,ptrs,sizeof(T)*sxyz); ptrs+=sxyz; }
+          }
+          resz.assign();
+        } else resc.assign(resz,true);
+
+        return resc._is_shared?(resz._is_shared?(resy._is_shared?(resx._is_shared?(+(*this)):resx):resy):resz):resc;
+      } break;
+
+        // Bicubic interpolation.
+        //
+      case 5 : {
+        const Tfloat vmin = (Tfloat)cimg::type<T>::min(), vmax = (Tfloat)cimg::type<T>::max();
+        CImg<uintT> off(cimg::max(sx,sy,sz,sc));
+        CImg<floatT> foff(off._width);
+        unsigned int *poff;
+        float *pfoff, old, curr;
+        CImg<T> resx, resy, resz, resc;
+        T *ptrd;
+
+        if (sx!=_width) {
+          if (_width==1) get_resize(sx,_height,_depth,_spectrum,1).move_to(resx);
+          else {
+            const float fx = (!border_conditions && sx>_width)?(sx>1?(_width-1.0f)/(sx-1):0):(float)_width/sx;
+            resx.assign(sx,_height,_depth,_spectrum);
+            curr = old = 0; poff = off._data; pfoff = foff._data;
+            cimg_forX(resx,x) { *(pfoff++) = curr - (unsigned int)curr; old = curr; curr+=fx; *(poff++) = (unsigned int)curr - (unsigned int)old; }
+            ptrd = resx._data;
+            const T *ptrs0 = _data;
+            cimg_forYZC(resx,y,z,c) {
+              poff = off._data; pfoff = foff._data;
+              const T *ptrs = ptrs0, *const ptrsmax = ptrs0 + (_width-2);
+              cimg_forX(resx,x) {
+                const float t = *(pfoff++);
+                const Tfloat
+                  val1 = (Tfloat)*ptrs,
+                  val0 = ptrs>ptrs0?(Tfloat)*(ptrs-1):val1,
+                  val2 = ptrs<=ptrsmax?(Tfloat)*(ptrs+1):val1,
+                  val3 = ptrs<ptrsmax?(Tfloat)*(ptrs+2):val2,
+                  val = val1 + 0.5f*(t*(-val0+val2) + t*t*(2*val0-5*val1+4*val2-val3) + t*t*t*(-val0+3*val1-3*val2+val3));
+                *(ptrd++) = (T)(val<vmin?vmin:val>vmax?vmax:val);
+                ptrs+=*(poff++);
+              }
+              ptrs0+=_width;
+            }
+          }
+        } else resx.assign(*this,true);
+
+        if (sy!=_height) {
+          if (_height==1) resx.get_resize(sx,sy,_depth,_spectrum,1).move_to(resy);
+          else {
+            const float fy = (!border_conditions && sy>_height)?(sy>1?(_height-1.0f)/(sy-1):0):(float)_height/sy;
+            resy.assign(sx,sy,_depth,_spectrum);
+            curr = old = 0; poff = off._data; pfoff = foff._data;
+            cimg_forY(resy,y) { *(pfoff++) = curr - (unsigned int)curr; old = curr; curr+=fy; *(poff++) = sx*((unsigned int)curr-(unsigned int)old); }
+            cimg_forXZC(resy,x,z,c) {
+              ptrd = resy.data(x,0,z,c);
+              const T *const ptrs0 = resx.data(x,0,z,c), *ptrs = ptrs0, *const ptrsmax = ptrs0 + (_height-2)*sx;
+              poff = off._data; pfoff = foff._data;
+              cimg_forY(resy,y) {
+                const float t = *(pfoff++);
+                const Tfloat
+                  val1 = (Tfloat)*ptrs,
+                  val0 = ptrs>ptrs0?(Tfloat)*(ptrs-sx):val1,
+                  val2 = ptrs<=ptrsmax?(Tfloat)*(ptrs+sx):val1,
+                  val3 = ptrs<ptrsmax?(Tfloat)*(ptrs+2*sx):val2,
+                  val = val1 + 0.5f*(t*(-val0+val2) + t*t*(2*val0-5*val1+4*val2-val3) + t*t*t*(-val0+3*val1-3*val2+val3));
+                *ptrd = (T)(val<vmin?vmin:val>vmax?vmax:val);
+                ptrd+=sx;
+                ptrs+=*(poff++);
+              }
+            }
+          }
+          resx.assign();
+        } else resy.assign(resx,true);
+
+        if (sz!=_depth) {
+          if (_depth==1) resy.get_resize(sx,sy,sz,_spectrum,1).move_to(resz);
+          else {
+            const float fz = (!border_conditions && sz>_depth)?(sz>1?(_depth-1.0f)/(sz-1):0):(float)_depth/sz;
+            const unsigned int sxy = sx*sy;
+            resz.assign(sx,sy,sz,_spectrum);
+            curr = old = 0; poff = off._data; pfoff = foff._data;
+            cimg_forZ(resz,z) { *(pfoff++) = curr - (unsigned int)curr; old = curr; curr+=fz; *(poff++) = sxy*((unsigned int)curr - (unsigned int)old); }
+            cimg_forXYC(resz,x,y,c) {
+              ptrd = resz.data(x,y,0,c);
+              const T *const ptrs0 = resy.data(x,y,0,c), *ptrs = ptrs0, *const ptrsmax = ptrs0 + (_depth-2)*sxy;
+              poff = off._data; pfoff = foff._data;
+              cimg_forZ(resz,z) {
+                const float t = *(pfoff++);
+                const Tfloat
+                  val1 = (Tfloat)*ptrs,
+                  val0 = ptrs>ptrs0?(Tfloat)*(ptrs-sxy):val1,
+                  val2 = ptrs<=ptrsmax?(Tfloat)*(ptrs+sxy):val1,
+                  val3 = ptrs<ptrsmax?(Tfloat)*(ptrs+2*sxy):val2,
+                  val = val1 + 0.5f*(t*(-val0+val2) + t*t*(2*val0-5*val1+4*val2-val3) + t*t*t*(-val0+3*val1-3*val2+val3));
+                *ptrd = (T)(val<vmin?vmin:val>vmax?vmax:val);
+                ptrd+=sxy;
+                ptrs+=*(poff++);
+              }
+            }
+          }
+          resy.assign();
+        } else resz.assign(resy,true);
+
+        if (sc!=_spectrum) {
+          if (_spectrum==1) resz.get_resize(sx,sy,sz,sc,1).move_to(resc);
+          else {
+            const float fc = (!border_conditions && sc>_spectrum)?(sc>1?(_spectrum-1.0f)/(sc-1):0):(float)_spectrum/sc;
+            const unsigned int sxyz = sx*sy*sz;
+            resc.assign(sx,sy,sz,sc);
+            curr = old = 0; poff = off._data; pfoff = foff._data;
+            cimg_forC(resc,c) { *(pfoff++) = curr - (unsigned int)curr; old = curr; curr+=fc; *(poff++) = sxyz*((unsigned int)curr - (unsigned int)old); }
+            cimg_forXYZ(resc,x,y,z) {
+              ptrd = resc.data(x,y,z,0);
+              const T *const ptrs0 = resz.data(x,y,z,0), *ptrs = ptrs0, *const ptrsmax = ptrs + (_spectrum-2)*sxyz;
+              poff = off._data; pfoff = foff._data;
+              cimg_forC(resc,c) {
+                const float t = *(pfoff++);
+                const Tfloat
+                  val1 = (Tfloat)*ptrs,
+                  val0 = ptrs>ptrs0?(Tfloat)*(ptrs-sxyz):val1,
+                  val2 = ptrs<=ptrsmax?(Tfloat)*(ptrs+sxyz):val1,
+                  val3 = ptrs<ptrsmax?(Tfloat)*(ptrs+2*sxyz):val2,
+                  val = val1 + 0.5f*(t*(-val0+val2) + t*t*(2*val0-5*val1+4*val2-val3) + t*t*t*(-val0+3*val1-3*val2+val3));
+                *ptrd = (T)(val<vmin?vmin:val>vmax?vmax:val);
+                ptrd+=sxyz;
+                ptrs+=*(poff++);
+              }
+            }
+          }
+          resz.assign();
+        } else resc.assign(resz,true);
+
+        return resc._is_shared?(resz._is_shared?(resy._is_shared?(resx._is_shared?(+(*this)):resx):resy):resz):resc;
+      } break;
+
+        // Lanczos interpolation.
+        //
+      case 6 : {
+        const Tfloat vmin = (Tfloat)cimg::type<T>::min(), vmax = (Tfloat)cimg::type<T>::max();
+        CImg<uintT> off(cimg::max(sx,sy,sz,sc));
+        CImg<floatT> foff(off._width);
+        unsigned int *poff;
+        float *pfoff, old, curr;
+        CImg<T> resx, resy, resz, resc;
+        T *ptrd;
+
+        if (sx!=_width) {
+          if (_width==1) get_resize(sx,_height,_depth,_spectrum,1).move_to(resx);
+          else {
+            const float fx = (!border_conditions && sx>_width)?(sx>1?(_width-1.0f)/(sx-1):0):(float)_width/sx;
+            resx.assign(sx,_height,_depth,_spectrum);
+            curr = old = 0; poff = off._data; pfoff = foff._data;
+            cimg_forX(resx,x) { *(pfoff++) = curr - (unsigned int)curr; old = curr; curr+=fx; *(poff++) = (unsigned int)curr - (unsigned int)old; }
+            ptrd = resx._data;
+            const T *ptrs0 = _data;
+            cimg_forYZC(resx,y,z,c) {
+              poff = off._data; pfoff = foff._data;
+              const T *ptrs = ptrs0, *const ptrsmin = ptrs0 + 1, *const ptrsmax = ptrs0 + (_width-2);
+              cimg_forX(resx,x) {
+                const float
+                  t = *(pfoff++),
+                  w0 = _cimg_lanczos(t+2),
+                  w1 = _cimg_lanczos(t+1),
+                  w2 = _cimg_lanczos(t),
+                  w3 = _cimg_lanczos(t-1),
+                  w4 = _cimg_lanczos(t-2);
+                const Tfloat
+                  val2 = (Tfloat)*ptrs,
+                  val1 = ptrs>=ptrsmin?(Tfloat)*(ptrs-1):val2,
+                  val0 = ptrs>ptrsmin?(Tfloat)*(ptrs-2):val1,
+                  val3 = ptrs<=ptrsmax?(Tfloat)*(ptrs+1):val2,
+                  val4 = ptrs<ptrsmax?(Tfloat)*(ptrs+2):val3,
+                  val = (val0*w0 + val1*w1 + val2*w2 + val3*w3 + val4*w4)/(w1 + w2 + w3 + w4);
+                *(ptrd++) = (T)(val<vmin?vmin:val>vmax?vmax:val);
+                ptrs+=*(poff++);
+              }
+              ptrs0+=_width;
+            }
+          }
+        } else resx.assign(*this,true);
+
+        if (sy!=_height) {
+          if (_height==1) resx.get_resize(sx,sy,_depth,_spectrum,1).move_to(resy);
+          else {
+            const float fy = (!border_conditions && sy>_height)?(sy>1?(_height-1.0f)/(sy-1):0):(float)_height/sy;
+            resy.assign(sx,sy,_depth,_spectrum);
+            curr = old = 0; poff = off._data; pfoff = foff._data;
+            cimg_forY(resy,y) { *(pfoff++) = curr - (unsigned int)curr; old = curr; curr+=fy; *(poff++) = sx*((unsigned int)curr-(unsigned int)old); }
+            cimg_forXZC(resy,x,z,c) {
+              ptrd = resy.data(x,0,z,c);
+              const T *const ptrs0 = resx.data(x,0,z,c), *ptrs = ptrs0, *const ptrsmin = ptrs0 + sx, *const ptrsmax = ptrs0 + (_height-2)*sx;
+              poff = off._data; pfoff = foff._data;
+              cimg_forY(resy,y) {
+                const float
+                  t = *(pfoff++),
+                  w0 = _cimg_lanczos(t+2),
+                  w1 = _cimg_lanczos(t+1),
+                  w2 = _cimg_lanczos(t),
+                  w3 = _cimg_lanczos(t-1),
+                  w4 = _cimg_lanczos(t-2);
+                const Tfloat
+                  val2 = (Tfloat)*ptrs,
+                  val1 = ptrs>=ptrsmin?(Tfloat)*(ptrs-sx):val2,
+                  val0 = ptrs>ptrsmin?(Tfloat)*(ptrs-2*sx):val1,
+                  val3 = ptrs<=ptrsmax?(Tfloat)*(ptrs+sx):val2,
+                  val4 = ptrs<ptrsmax?(Tfloat)*(ptrs+2*sx):val3,
+                  val = (val0*w0 + val1*w1 + val2*w2 + val3*w3 + val4*w4)/(w1 + w2 + w3 + w4);
+                *ptrd = (T)(val<vmin?vmin:val>vmax?vmax:val);
+                ptrd+=sx;
+                ptrs+=*(poff++);
+              }
+            }
+          }
+          resx.assign();
+        } else resy.assign(resx,true);
+
+        if (sz!=_depth) {
+          if (_depth==1) resy.get_resize(sx,sy,sz,_spectrum,1).move_to(resz);
+          else {
+            const float fz = (!border_conditions && sz>_depth)?(sz>1?(_depth-1.0f)/(sz-1):0):(float)_depth/sz;
+            const unsigned int sxy = sx*sy;
+            resz.assign(sx,sy,sz,_spectrum);
+            curr = old = 0; poff = off._data; pfoff = foff._data;
+            cimg_forZ(resz,z) { *(pfoff++) = curr - (unsigned int)curr; old = curr; curr+=fz; *(poff++) = sxy*((unsigned int)curr - (unsigned int)old); }
+            cimg_forXYC(resz,x,y,c) {
+              ptrd = resz.data(x,y,0,c);
+              const T *const ptrs0 = resy.data(x,y,0,c), *ptrs = ptrs0, *const ptrsmin = ptrs0 + sxy, *const ptrsmax = ptrs0 + (_depth-2)*sxy;
+              poff = off._data; pfoff = foff._data;
+              cimg_forZ(resz,z) {
+                const float
+                  t = *(pfoff++),
+                  w0 = _cimg_lanczos(t+2),
+                  w1 = _cimg_lanczos(t+1),
+                  w2 = _cimg_lanczos(t),
+                  w3 = _cimg_lanczos(t-1),
+                  w4 = _cimg_lanczos(t-2);
+                const Tfloat
+                  val2 = (Tfloat)*ptrs,
+                  val1 = ptrs>=ptrsmin?(Tfloat)*(ptrs-sxy):val2,
+                  val0 = ptrs>ptrsmin?(Tfloat)*(ptrs-2*sxy):val1,
+                  val3 = ptrs<=ptrsmax?(Tfloat)*(ptrs+sxy):val2,
+                  val4 = ptrs<ptrsmax?(Tfloat)*(ptrs+2*sxy):val3,
+                  val = (val0*w0 + val1*w1 + val2*w2 + val3*w3 + val4*w4)/(w1 + w2 + w3 + w4);
+                *ptrd = (T)(val<vmin?vmin:val>vmax?vmax:val);
+                ptrd+=sxy;
+                ptrs+=*(poff++);
+              }
+            }
+          }
+          resy.assign();
+        } else resz.assign(resy,true);
+
+        if (sc!=_spectrum) {
+          if (_spectrum==1) resz.get_resize(sx,sy,sz,sc,1).move_to(resc);
+          else {
+            const float fc = (!border_conditions && sc>_spectrum)?(sc>1?(_spectrum-1.0f)/(sc-1):0):(float)_spectrum/sc;
+            const unsigned int sxyz = sx*sy*sz;
+            resc.assign(sx,sy,sz,sc);
+            curr = old = 0; poff = off._data; pfoff = foff._data;
+            cimg_forC(resc,c) { *(pfoff++) = curr - (unsigned int)curr; old = curr; curr+=fc; *(poff++) = sxyz*((unsigned int)curr - (unsigned int)old); }
+            cimg_forXYZ(resc,x,y,z) {
+              ptrd = resc.data(x,y,z,0);
+              const T *const ptrs0 = resz.data(x,y,z,0), *ptrs = ptrs0, *const ptrsmin = ptrs0 + sxyz, *const ptrsmax = ptrs + (_spectrum-2)*sxyz;
+              poff = off._data; pfoff = foff._data;
+              cimg_forC(resc,c) {
+                const float
+                  t = *(pfoff++),
+                  w0 = _cimg_lanczos(t+2),
+                  w1 = _cimg_lanczos(t+1),
+                  w2 = _cimg_lanczos(t),
+                  w3 = _cimg_lanczos(t-1),
+                  w4 = _cimg_lanczos(t-2);
+                const Tfloat
+                  val2 = (Tfloat)*ptrs,
+                  val1 = ptrs>=ptrsmin?(Tfloat)*(ptrs-sxyz):val2,
+                  val0 = ptrs>ptrsmin?(Tfloat)*(ptrs-2*sxyz):val1,
+                  val3 = ptrs<=ptrsmax?(Tfloat)*(ptrs+sxyz):val2,
+                  val4 = ptrs<ptrsmax?(Tfloat)*(ptrs+2*sxyz):val3,
+                  val = (val0*w0 + val1*w1 + val2*w2 + val3*w3 + val4*w4)/(w1 + w2 + w3 + w4);
+                *ptrd = (T)(val<vmin?vmin:val>vmax?vmax:val);
+                ptrd+=sxyz;
+                ptrs+=*(poff++);
+              }
+            }
+          }
+          resz.assign();
+        } else resc.assign(resz,true);
+
+        return resc._is_shared?(resz._is_shared?(resy._is_shared?(resx._is_shared?(+(*this)):resx):resy):resz):resc;
+      } break;
+
+        // Unknow interpolation.
+        //
+      default :
+        throw CImgArgumentException(_cimg_instance
+                                    "resize() : Invalid specified interpolation %d "
+                                    "(should be { -1=raw | 0=none | 1=nearest | 2=average | 3=linear | 4=grid | 5=bicubic | 6=lanczos }).",
+                                    cimg_instance,
+                                    interpolation_type);
+      }
+      return res;
+    }
+
+    //! Resize an image.
+    template<typename t>
+    CImg<T>& resize(const CImg<t>& src,
+                    const int interpolation_type=1, const unsigned int border_conditions=0,
+                    const float centering_x = 0, const float centering_y = 0,
+                    const float centering_z = 0, const float centering_c = 0) {
+      return resize(src._width,src._height,src._depth,src._spectrum,interpolation_type,border_conditions,
+                    centering_x,centering_y,centering_z,centering_c);
+    }
+
+    template<typename t>
+    CImg<T> get_resize(const CImg<t>& src,
+                       const int interpolation_type=1, const unsigned int border_conditions=0,
+                       const float centering_x = 0, const float centering_y = 0,
+                       const float centering_z = 0, const float centering_c = 0) const {
+      return get_resize(src._width,src._height,src._depth,src._spectrum,interpolation_type,border_conditions,
+                        centering_x,centering_y,centering_z,centering_c);
+    }
+
+    //! Resize an image.
+    CImg<T>& resize(const CImgDisplay& disp,
+                    const int interpolation_type=1, const unsigned int border_conditions=0,
+                    const float centering_x = 0, const float centering_y = 0,
+                    const float centering_z = 0, const float centering_c = 0) {
+      return resize(disp.width(),disp.height(),_depth,_spectrum,interpolation_type,border_conditions,
+                    centering_x,centering_y,centering_z,centering_c);
+    }
+
+    CImg<T> get_resize(const CImgDisplay& disp,
+                       const int interpolation_type=1, const unsigned int border_conditions=0,
+                       const float centering_x = 0, const float centering_y = 0,
+                       const float centering_z = 0, const float centering_c = 0) const {
+      return get_resize(disp.width(),disp.height(),_depth,_spectrum,interpolation_type,border_conditions,
+                        centering_x,centering_y,centering_z,centering_c);
+    }
+
+    //! Half-resize an image, using a special optimized filter.
+    CImg<T>& resize_halfXY() {
+      return get_resize_halfXY().move_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> res(_width/2,_height/2,_depth,_spectrum);
+      T *ptrd = res._data;
+      cimg_forZC(*this,z,c) cimg_for3x3(*this,x,y,z,c,I,T)
+        if (x%2 && y%2) *(ptrd++) = (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 res;
+    }
+
+    //! 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().move_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,c,I,T) \
+  _cimg_gs2x_for3((img)._height,y) for (int x = 0, \
+   _p1##x = 0, \
+   _n1##x = (int)( \
+   (I[1] = (T)(img)(_p1##x,_p1##y,z,c)), \
+   (I[3] = I[4] = (T)(img)(0,y,z,c)), \
+   (I[7] = (T)(img)(0,_n1##y,z,c)),     \
+   1>=(img)._width?(img).width()-1:1); \
+   (_n1##x<(img).width() && ( \
+   (I[2] = (T)(img)(_n1##x,_p1##y,z,c)), \
+   (I[5] = (T)(img)(_n1##x,y,z,c)), \
+   (I[8] = (T)(img)(_n1##x,_n1##y,z,c)),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(_width<<1,_height<<1,_depth,_spectrum);
+      CImg_3x3(I,T);
+      cimg_forZC(*this,z,c) {
+        T
+          *ptrd1 = res.data(0,0,z,c),
+          *ptrd2 = ptrd1 + res._width;
+        _cimg_gs2x_for3x3(*this,x,y,z,c,I,T) {
+          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().move_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,c,I,T) \
+  _cimg_gs3x_for3((img)._height,y) for (int x = 0, \
+   _p1##x = 0, \
+   _n1##x = (int)( \
+   (I[0] = I[1] = (T)(img)(_p1##x,_p1##y,z,c)), \
+   (I[3] = I[4] = (T)(img)(0,y,z,c)), \
+   (I[6] = I[7] = (T)(img)(0,_n1##y,z,c)),      \
+   1>=(img)._width?(img).width()-1:1); \
+   (_n1##x<(img).width() && ( \
+   (I[2] = (T)(img)(_n1##x,_p1##y,z,c)), \
+   (I[5] = (T)(img)(_n1##x,y,z,c)), \
+   (I[8] = (T)(img)(_n1##x,_n1##y,z,c)),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,_spectrum);
+      CImg_3x3(I,T);
+      cimg_forZC(*this,z,c) {
+        T
+          *ptrd1 = res.data(0,0,z,c),
+          *ptrd2 = ptrd1 + res._width,
+          *ptrd3 = ptrd2 + res._width;
+        _cimg_gs3x_for3x3(*this,x,y,z,c,I,T) {
+          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;
+    }
+
+    //! 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 = data(_width-1);
+        const unsigned int width2 = _width/2;
+        for (unsigned int yzv = 0; yzv<_height*_depth*_spectrum; ++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 = data(0,_height-1);
+        const unsigned int height2 = _height/2;
+        for (unsigned int zv = 0; zv<_depth*_spectrum; ++zv) {
+          for (unsigned int y = 0; y<height2; ++y) {
+            std::memcpy(buf,pf,_width*sizeof(T));
+            std::memcpy(pf,pb,_width*sizeof(T));
+            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 = data(0,0,_depth-1);
+        const unsigned int depth2 = _depth/2;
+        cimg_forC(*this,c) {
+          for (unsigned int z = 0; z<depth2; ++z) {
+            std::memcpy(buf,pf,_width*_height*sizeof(T));
+            std::memcpy(pf,pb,_width*_height*sizeof(T));
+            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 'c' : {
+        buf = new T[_width*_height*_depth];
+        pf = _data; pb = data(0,0,0,_spectrum-1);
+        const unsigned int _spectrum2 = _spectrum/2;
+        for (unsigned int v = 0; v<_spectrum2; ++v) {
+          std::memcpy(buf,pf,_width*_height*_depth*sizeof(T));
+          std::memcpy(pf,pb,_width*_height*_depth*sizeof(T));
+          std::memcpy(pb,buf,_width*_height*_depth*sizeof(T));
+          pf+=_width*_height*_depth;
+          pb-=_width*_height*_depth;
+        }
+      } break;
+      default :
+        throw CImgArgumentException(_cimg_instance
+                                    "mirror() : Invalid specified axis '%c'.",
+                                    cimg_instance,
+                                    axis);
+      }
+      delete[] buf;
+      return *this;
+    }
+
+    CImg<T> get_mirror(const char axis) const {
+      return (+*this).mirror(axis);
+    }
+
+    //! Mirror image along specified axes.
+    CImg<T>& mirror(const char *const axes) {
+      for (const char *s = axes; *s; s++) mirror(*s);
+      return *this;
+    }
+
+    CImg<T> get_mirror(const char *const axes) const {
+      return (+*this).mirror(axes);
+    }
+
+    //! Shift 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 deltac Amount of displacement along the C-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>& shift(const int deltax, const int deltay=0, const int deltaz=0, const int deltac=0,
+                   const int border_condition=0) {
+      if (is_empty()) return *this;
+      if (deltax) // Shift along X-axis
+        switch (border_condition) {
+        case 0 :
+          if (cimg::abs(deltax)>=width()) return fill(0);
+          if (deltax<0) cimg_forYZC(*this,y,z,c) {
+            std::memmove(data(0,y,z,c),data(-deltax,y,z,c),(_width+deltax)*sizeof(T));
+            std::memset(data(_width+deltax,y,z,c),0,-deltax*sizeof(T));
+          } else cimg_forYZC(*this,y,z,c) {
+            std::memmove(data(deltax,y,z,c),data(0,y,z,c),(_width-deltax)*sizeof(T));
+            std::memset(data(0,y,z,c),0,deltax*sizeof(T));
+          }
+          break;
+        case 1 :
+          if (deltax<0) {
+            const int ndeltax = (-deltax>=width())?width()-1:-deltax;
+            if (!ndeltax) return *this;
+            cimg_forYZC(*this,y,z,c) {
+              std::memmove(data(0,y,z,c),data(ndeltax,y,z,c),(_width-ndeltax)*sizeof(T));
+              T *ptrd = data(_width-1,y,z,c);
+              const T val = *ptrd;
+              for (int l = 0; l<ndeltax-1; ++l) *(--ptrd) = val;
+            }
+          } else {
+            const int ndeltax = (deltax>=width())?width()-1:deltax;
+            if (!ndeltax) return *this;
+            cimg_forYZC(*this,y,z,c) {
+              std::memmove(data(ndeltax,y,z,c),data(0,y,z,c),(_width-ndeltax)*sizeof(T));
+              T *ptrd = data(0,y,z,c);
+              const T val = *ptrd;
+              for (int l = 0; l<ndeltax-1; ++l) *(++ptrd) = val;
+            }
+          }
+          break;
+        default : {
+          const int ml = cimg::mod(-deltax,width()), ndeltax = (ml<=width()/2)?ml:(ml-width());
+          if (!ndeltax) return *this;
+          T *const buf = new T[cimg::abs(ndeltax)];
+          if (ndeltax>0) cimg_forYZC(*this,y,z,c) {
+            std::memcpy(buf,data(0,y,z,c),ndeltax*sizeof(T));
+            std::memmove(data(0,y,z,c),data(ndeltax,y,z,c),(_width-ndeltax)*sizeof(T));
+            std::memcpy(data(_width-ndeltax,y,z,c),buf,ndeltax*sizeof(T));
+          } else cimg_forYZC(*this,y,z,c) {
+            std::memcpy(buf,data(_width+ndeltax,y,z,c),-ndeltax*sizeof(T));
+            std::memmove(data(-ndeltax,y,z,c),data(0,y,z,c),(_width+ndeltax)*sizeof(T));
+            std::memcpy(data(0,y,z,c),buf,-ndeltax*sizeof(T));
+          }
+          delete[] buf;
+        }
+        }
+
+      if (deltay) // Shift along Y-axis
+        switch (border_condition) {
+        case 0 :
+          if (cimg::abs(deltay)>=height()) return fill(0);
+          if (deltay<0) cimg_forZC(*this,z,c) {
+            std::memmove(data(0,0,z,c),data(0,-deltay,z,c),_width*(_height+deltay)*sizeof(T));
+            std::memset(data(0,_height+deltay,z,c),0,-deltay*_width*sizeof(T));
+          } else cimg_forZC(*this,z,c) {
+            std::memmove(data(0,deltay,z,c),data(0,0,z,c),_width*(_height-deltay)*sizeof(T));
+            std::memset(data(0,0,z,c),0,deltay*_width*sizeof(T));
+          }
+          break;
+        case 1 :
+          if (deltay<0) {
+            const int ndeltay = (-deltay>=height())?height()-1:-deltay;
+            if (!ndeltay) return *this;
+            cimg_forZC(*this,z,c) {
+              std::memmove(data(0,0,z,c),data(0,ndeltay,z,c),_width*(_height-ndeltay)*sizeof(T));
+              T *ptrd = data(0,_height-ndeltay,z,c), *ptrs = data(0,_height-1,z,c);
+              for (int l = 0; l<ndeltay-1; ++l) { std::memcpy(ptrd,ptrs,_width*sizeof(T)); ptrd+=_width; }
+            }
+          } else {
+            const int ndeltay = (deltay>=height())?height()-1:deltay;
+            if (!ndeltay) return *this;
+            cimg_forZC(*this,z,c) {
+              std::memmove(data(0,ndeltay,z,c),data(0,0,z,c),_width*(_height-ndeltay)*sizeof(T));
+              T *ptrd = data(0,1,z,c), *ptrs = data(0,0,z,c);
+              for (int l = 0; l<ndeltay-1; ++l) { std::memcpy(ptrd,ptrs,_width*sizeof(T)); ptrd+=_width; }
+            }
+          }
+          break;
+        default : {
+          const int ml = cimg::mod(-deltay,height()), ndeltay = (ml<=height()/2)?ml:(ml-height());
+          if (!ndeltay) return *this;
+          T *const buf = new T[_width*cimg::abs(ndeltay)];
+          if (ndeltay>0) cimg_forZC(*this,z,c) {
+            std::memcpy(buf,data(0,0,z,c),_width*ndeltay*sizeof(T));
+            std::memmove(data(0,0,z,c),data(0,ndeltay,z,c),_width*(_height-ndeltay)*sizeof(T));
+            std::memcpy(data(0,_height-ndeltay,z,c),buf,_width*ndeltay*sizeof(T));
+          } else cimg_forZC(*this,z,c) {
+            std::memcpy(buf,data(0,_height+ndeltay,z,c),-ndeltay*_width*sizeof(T));
+            std::memmove(data(0,-ndeltay,z,c),data(0,0,z,c),_width*(_height+ndeltay)*sizeof(T));
+            std::memcpy(data(0,0,z,c),buf,-ndeltay*_width*sizeof(T));
+          }
+          delete[] buf;
+        }
+        }
+
+      if (deltaz) // Shift along Z-axis
+        switch (border_condition) {
+        case 0 :
+          if (cimg::abs(deltaz)>=depth()) return fill(0);
+          if (deltaz<0) cimg_forC(*this,c) {
+            std::memmove(data(0,0,0,c),data(0,0,-deltaz,c),_width*_height*(_depth+deltaz)*sizeof(T));
+            std::memset(data(0,0,_depth+deltaz,c),0,_width*_height*(-deltaz)*sizeof(T));
+          } else cimg_forC(*this,c) {
+            std::memmove(data(0,0,deltaz,c),data(0,0,0,c),_width*_height*(_depth-deltaz)*sizeof(T));
+            std::memset(data(0,0,0,c),0,deltaz*_width*_height*sizeof(T));
+          }
+          break;
+        case 1 :
+          if (deltaz<0) {
+            const int ndeltaz = (-deltaz>=depth())?depth()-1:-deltaz;
+            if (!ndeltaz) return *this;
+            cimg_forC(*this,c) {
+              std::memmove(data(0,0,0,c),data(0,0,ndeltaz,c),_width*_height*(_depth-ndeltaz)*sizeof(T));
+              T *ptrd = data(0,0,_depth-ndeltaz,c), *ptrs = data(0,0,_depth-1,c);
+              for (int l = 0; l<ndeltaz-1; ++l) { std::memcpy(ptrd,ptrs,_width*_height*sizeof(T)); ptrd+=_width*_height; }
+            }
+          } else {
+            const int ndeltaz = (deltaz>=depth())?depth()-1:deltaz;
+            if (!ndeltaz) return *this;
+            cimg_forC(*this,c) {
+              std::memmove(data(0,0,ndeltaz,c),data(0,0,0,c),_width*_height*(_depth-ndeltaz)*sizeof(T));
+              T *ptrd = data(0,0,1,c), *ptrs = data(0,0,0,c);
+              for (int l = 0; l<ndeltaz-1; ++l) { std::memcpy(ptrd,ptrs,_width*_height*sizeof(T)); ptrd+=_width*_height; }
+            }
+          }
+          break;
+        default : {
+          const int ml = cimg::mod(-deltaz,depth()), ndeltaz = (ml<=depth()/2)?ml:(ml-depth());
+          if (!ndeltaz) return *this;
+          T *const buf = new T[_width*_height*cimg::abs(ndeltaz)];
+          if (ndeltaz>0) cimg_forC(*this,c) {
+            std::memcpy(buf,data(0,0,0,c),_width*_height*ndeltaz*sizeof(T));
+            std::memmove(data(0,0,0,c),data(0,0,ndeltaz,c),_width*_height*(_depth-ndeltaz)*sizeof(T));
+            std::memcpy(data(0,0,_depth-ndeltaz,c),buf,_width*_height*ndeltaz*sizeof(T));
+          } else cimg_forC(*this,c) {
+            std::memcpy(buf,data(0,0,_depth+ndeltaz,c),-ndeltaz*_width*_height*sizeof(T));
+            std::memmove(data(0,0,-ndeltaz,c),data(0,0,0,c),_width*_height*(_depth+ndeltaz)*sizeof(T));
+            std::memcpy(data(0,0,0,c),buf,-ndeltaz*_width*_height*sizeof(T));
+          }
+          delete[] buf;
+        }
+        }
+
+      if (deltac) // Shift along C-axis
+        switch (border_condition) {
+        case 0 :
+          if (cimg::abs(deltac)>=spectrum()) return fill(0);
+          if (deltac<0) {
+            std::memmove(_data,data(0,0,0,-deltac),_width*_height*_depth*(_spectrum+deltac)*sizeof(T));
+            std::memset(data(0,0,0,_spectrum+deltac),0,_width*_height*_depth*(-deltac)*sizeof(T));
+          } else {
+            std::memmove(data(0,0,0,deltac),_data,_width*_height*_depth*(_spectrum-deltac)*sizeof(T));
+            std::memset(_data,0,deltac*_width*_height*_depth*sizeof(T));
+          }
+          break;
+        case 1 :
+          if (deltac<0) {
+            const int ndeltac = (-deltac>=spectrum())?spectrum()-1:-deltac;
+            if (!ndeltac) return *this;
+            std::memmove(_data,data(0,0,0,ndeltac),_width*_height*_depth*(_spectrum-ndeltac)*sizeof(T));
+            T *ptrd = data(0,0,0,_spectrum-ndeltac), *ptrs = data(0,0,0,_spectrum-1);
+            for (int l = 0; l<ndeltac-1; ++l) { std::memcpy(ptrd,ptrs,_width*_height*_depth*sizeof(T)); ptrd+=_width*_height*_depth; }
+          } else {
+            const int ndeltac = (deltac>=spectrum())?spectrum()-1:deltac;
+            if (!ndeltac) return *this;
+            std::memmove(data(0,0,0,ndeltac),_data,_width*_height*_depth*(_spectrum-ndeltac)*sizeof(T));
+            T *ptrd = data(0,0,0,1);
+            for (int l = 0; l<ndeltac-1; ++l) { std::memcpy(ptrd,_data,_width*_height*_depth*sizeof(T)); ptrd+=_width*_height*_depth; }
+          }
+          break;
+        default : {
+          const int ml = cimg::mod(-deltac,spectrum()), ndeltac = (ml<=spectrum()/2)?ml:(ml-spectrum());
+          if (!ndeltac) return *this;
+          T *const buf = new T[_width*_height*_depth*cimg::abs(ndeltac)];
+          if (ndeltac>0) {
+            std::memcpy(buf,_data,_width*_height*_depth*ndeltac*sizeof(T));
+            std::memmove(_data,data(0,0,0,ndeltac),_width*_height*_depth*(_spectrum-ndeltac)*sizeof(T));
+            std::memcpy(data(0,0,0,_spectrum-ndeltac),buf,_width*_height*_depth*ndeltac*sizeof(T));
+          } else {
+            std::memcpy(buf,data(0,0,0,_spectrum+ndeltac),-ndeltac*_width*_height*_depth*sizeof(T));
+            std::memmove(data(0,0,0,-ndeltac),_data,_width*_height*_depth*(_spectrum+ndeltac)*sizeof(T));
+            std::memcpy(_data,buf,-ndeltac*_width*_height*_depth*sizeof(T));
+          }
+          delete[] buf;
+        }
+        }
+      return *this;
+    }
+
+    CImg<T> get_shift(const int deltax, const int deltay=0, const int deltaz=0, const int deltac=0,
+                          const int border_condition=0) const {
+      return (+*this).shift(deltax,deltay,deltaz,deltac,border_condition);
+    }
+
+    // Permute axes order (internal).
+    template<typename t>
+    CImg<t> _get_permute_axes(const char *const permut, const t&) const {
+      if (is_empty() || !permut) return CImg<t>(*this,false);
+      CImg<t> res;
+      const T* ptrs = _data;
+      if (!cimg::strncasecmp(permut,"xyzc",4)) return +*this;
+      if (!cimg::strncasecmp(permut,"xycz",4)) {
+        res.assign(_width,_height,_spectrum,_depth);
+        const unsigned long wh = (unsigned long)res._width*res._height, whd = wh*res._depth;
+        cimg_forXYZC(*this,x,y,z,c) res(x,y,c,z,wh,whd) = (t)*(ptrs++);
+      }
+      if (!cimg::strncasecmp(permut,"xzyc",4)) {
+        res.assign(_width,_depth,_height,_spectrum);
+        const unsigned long wh = (unsigned long)res._width*res._height, whd = wh*res._depth;
+        cimg_forXYZC(*this,x,y,z,c) res(x,z,y,c,wh,whd) = (t)*(ptrs++);
+      }
+      if (!cimg::strncasecmp(permut,"xzcy",4)) {
+        res.assign(_width,_depth,_spectrum,_height);
+        const unsigned long wh = (unsigned long)res._width*res._height, whd = wh*res._depth;
+        cimg_forXYZC(*this,x,y,z,c) res(x,z,c,y,wh,whd) = (t)*(ptrs++);
+      }
+      if (!cimg::strncasecmp(permut,"xcyz",4)) {
+        res.assign(_width,_spectrum,_height,_depth);
+        const unsigned long wh = (unsigned long)res._width*res._height, whd = wh*res._depth;
+        cimg_forXYZC(*this,x,y,z,c) res(x,c,y,z,wh,whd) = (t)*(ptrs++);
+      }
+      if (!cimg::strncasecmp(permut,"xczy",4)) {
+        res.assign(_width,_spectrum,_depth,_height);
+        const unsigned long wh = (unsigned long)res._width*res._height, whd = wh*res._depth;
+        cimg_forXYZC(*this,x,y,z,c) res(x,c,z,y,wh,whd) = (t)*(ptrs++);
+      }
+      if (!cimg::strncasecmp(permut,"yxzc",4)) {
+        res.assign(_height,_width,_depth,_spectrum);
+        const unsigned long wh = (unsigned long)res._width*res._height, whd = wh*res._depth;
+        cimg_forXYZC(*this,x,y,z,c) res(y,x,z,c,wh,whd) = (t)*(ptrs++);
+      }
+      if (!cimg::strncasecmp(permut,"yxcz",4)) {
+        res.assign(_height,_width,_spectrum,_depth);
+        const unsigned long wh = (unsigned long)res._width*res._height, whd = wh*res._depth;
+        cimg_forXYZC(*this,x,y,z,c) res(y,x,c,z,wh,whd) = (t)*(ptrs++);
+      }
+      if (!cimg::strncasecmp(permut,"yzxc",4)) {
+        res.assign(_height,_depth,_width,_spectrum);
+        const unsigned long wh = (unsigned long)res._width*res._height, whd = wh*res._depth;
+        cimg_forXYZC(*this,x,y,z,c) res(y,z,x,c,wh,whd) = (t)*(ptrs++);
+      }
+      if (!cimg::strncasecmp(permut,"yzcx",4)) {
+        res.assign(_height,_depth,_spectrum,_width);
+        switch (_width) {
+        case 1 : {
+          t *ptr_r = res.data(0,0,0,0);
+          for (unsigned int siz = _height*_depth*_spectrum; siz; --siz) {
+            *(ptr_r++) = (t)*(ptrs++);
+          }
+        } break;
+        case 2 : {
+          t *ptr_r = res.data(0,0,0,0), *ptr_g = res.data(0,0,0,1);
+          for (unsigned int siz = _height*_depth*_spectrum; siz; --siz) {
+            *(ptr_r++) = (t)*(ptrs++); *(ptr_g++) = (t)*(ptrs++);
+          }
+        } break;
+        case 3 : { // Optimization for the classical conversion from interleaved RGB to planar RGB
+          t *ptr_r = res.data(0,0,0,0), *ptr_g = res.data(0,0,0,1), *ptr_b = res.data(0,0,0,2);
+          for (unsigned int siz = _height*_depth*_spectrum; siz; --siz) {
+            *(ptr_r++) = (t)*(ptrs++); *(ptr_g++) = (t)*(ptrs++); *(ptr_b++) = (t)*(ptrs++);
+          }
+        } break;
+        case 4 : { // Optimization for the classical conversion from interleaved RGBA to planar RGBA
+          t *ptr_r = res.data(0,0,0,0), *ptr_g = res.data(0,0,0,1), *ptr_b = res.data(0,0,0,2), *ptr_a = res.data(0,0,0,3);
+          for (unsigned int siz = _height*_depth*_spectrum; siz; --siz) {
+            *(ptr_r++) = (t)*(ptrs++); *(ptr_g++) = (t)*(ptrs++); *(ptr_b++) = (t)*(ptrs++); *(ptr_a++) = (t)*(ptrs++);
+          }
+        } break;
+        default : {
+          const unsigned long wh = (unsigned long)res._width*res._height, whd = wh*res._depth;
+          cimg_forXYZC(*this,x,y,z,c) res(y,z,c,x,wh,whd) = *(ptrs++);
+          return res;
+        }
+        }
+      }
+      if (!cimg::strncasecmp(permut,"ycxz",4)) {
+        res.assign(_height,_spectrum,_width,_depth);
+        const unsigned long wh = (unsigned long)res._width*res._height, whd = wh*res._depth;
+        cimg_forXYZC(*this,x,y,z,c) res(y,c,x,z,wh,whd) = (t)*(ptrs++);
+      }
+      if (!cimg::strncasecmp(permut,"yczx",4)) {
+        res.assign(_height,_spectrum,_depth,_width);
+        const unsigned long wh = (unsigned long)res._width*res._height, whd = wh*res._depth;
+        cimg_forXYZC(*this,x,y,z,c) res(y,c,z,x,wh,whd) = (t)*(ptrs++);
+      }
+      if (!cimg::strncasecmp(permut,"zxyc",4)) {
+        res.assign(_depth,_width,_height,_spectrum);
+        const unsigned long wh = (unsigned long)res._width*res._height, whd = wh*res._depth;
+        cimg_forXYZC(*this,x,y,z,c) res(z,x,y,c,wh,whd) = (t)*(ptrs++);
+      }
+      if (!cimg::strncasecmp(permut,"zxcy",4)) {
+        res.assign(_depth,_width,_spectrum,_height);
+        const unsigned long wh = (unsigned long)res._width*res._height, whd = wh*res._depth;
+        cimg_forXYZC(*this,x,y,z,c) res(z,x,c,y,wh,whd) = (t)*(ptrs++);
+      }
+      if (!cimg::strncasecmp(permut,"zyxc",4)) {
+        res.assign(_depth,_height,_width,_spectrum);
+        const unsigned long wh = (unsigned long)res._width*res._height, whd = wh*res._depth;
+        cimg_forXYZC(*this,x,y,z,c) res(z,y,x,c,wh,whd) = (t)*(ptrs++);
+      }
+      if (!cimg::strncasecmp(permut,"zycx",4)) {
+        res.assign(_depth,_height,_spectrum,_width);
+        const unsigned long wh = (unsigned long)res._width*res._height, whd = wh*res._depth;
+        cimg_forXYZC(*this,x,y,z,c) res(z,y,c,x,wh,whd) = (t)*(ptrs++);
+      }
+      if (!cimg::strncasecmp(permut,"zcxy",4)) {
+        res.assign(_depth,_spectrum,_width,_height);
+        const unsigned long wh = (unsigned long)res._width*res._height, whd = wh*res._depth;
+        cimg_forXYZC(*this,x,y,z,c) res(z,c,x,y,wh,whd) = (t)*(ptrs++);
+      }
+      if (!cimg::strncasecmp(permut,"zcyx",4)) {
+        res.assign(_depth,_spectrum,_height,_width);
+        const unsigned long wh = (unsigned long)res._width*res._height, whd = wh*res._depth;
+        cimg_forXYZC(*this,x,y,z,c) res(z,c,y,x,wh,whd) = (t)*(ptrs++);
+      }
+      if (!cimg::strncasecmp(permut,"cxyz",4)) {
+        res.assign(_spectrum,_width,_height,_depth);
+        switch (_spectrum) {
+        case 1 : {
+          const T *ptr_r = data(0,0,0,0);
+          t *ptrd = res._data;
+          for (unsigned int siz = _width*_height*_depth; siz; --siz) {
+            *(ptrd++) = (t)*(ptr_r++);
+          }
+        } break;
+        case 2 : {
+          const T *ptr_r = data(0,0,0,0), *ptr_g = data(0,0,0,1);
+          t *ptrd = res._data;
+          for (unsigned int siz = _width*_height*_depth; siz; --siz) {
+            *(ptrd++) = (t)*(ptr_r++); *(ptrd++) = (t)*(ptr_g++);
+          }
+        } break;
+        case 3 : { // Optimization for the classical conversion from planar RGB to interleaved RGB
+          const T *ptr_r = data(0,0,0,0), *ptr_g = data(0,0,0,1), *ptr_b = data(0,0,0,2);
+          t *ptrd = res._data;
+          for (unsigned int siz = _width*_height*_depth; siz; --siz) {
+            *(ptrd++) = (t)*(ptr_r++); *(ptrd++) = (t)*(ptr_g++); *(ptrd++) = (t)*(ptr_b++);
+          }
+        } break;
+        case 4 : { // Optimization for the classical conversion from planar RGBA to interleaved RGBA
+          const T *ptr_r = data(0,0,0,0), *ptr_g = data(0,0,0,1), *ptr_b = data(0,0,0,2), *ptr_a = data(0,0,0,3);
+          t *ptrd = res._data;
+          for (unsigned int siz = _width*_height*_depth; siz; --siz) {
+            *(ptrd++) = (t)*(ptr_r++); *(ptrd++) = (t)*(ptr_g++); *(ptrd++) = (t)*(ptr_b++); *(ptrd++) = (t)*(ptr_a++);
+          }
+        } break;
+        default : {
+          const unsigned long wh = (unsigned long)res._width*res._height, whd = wh*res._depth;
+          cimg_forXYZC(*this,x,y,z,c) res(c,x,y,z,wh,whd) = (t)*(ptrs++);
+        }
+        }
+      }
+      if (!cimg::strncasecmp(permut,"cxzy",4)) {
+        res.assign(_spectrum,_width,_depth,_height);
+        const unsigned long wh = (unsigned long)res._width*res._height, whd = wh*res._depth;
+        cimg_forXYZC(*this,x,y,z,c) res(c,x,z,y,wh,whd) = (t)*(ptrs++);
+      }
+      if (!cimg::strncasecmp(permut,"cyxz",4)) {
+        res.assign(_spectrum,_height,_width,_depth);
+        const unsigned long wh = (unsigned long)res._width*res._height, whd = wh*res._depth;
+        cimg_forXYZC(*this,x,y,z,c) res(c,y,x,z,wh,whd) = (t)*(ptrs++);
+      }
+      if (!cimg::strncasecmp(permut,"cyzx",4)) {
+        res.assign(_spectrum,_height,_depth,_width);
+        const unsigned long wh = (unsigned long)res._width*res._height, whd = wh*res._depth;
+        cimg_forXYZC(*this,x,y,z,c) res(c,y,z,x,wh,whd) = (t)*(ptrs++);
+      }
+      if (!cimg::strncasecmp(permut,"czxy",4)) {
+        res.assign(_spectrum,_depth,_width,_height);
+        const unsigned long wh = (unsigned long)res._width*res._height, whd = wh*res._depth;
+        cimg_forXYZC(*this,x,y,z,c) res(c,z,x,y,wh,whd) = (t)*(ptrs++);
+      }
+      if (!cimg::strncasecmp(permut,"czyx",4)) {
+        res.assign(_spectrum,_depth,_height,_width);
+        const unsigned long wh = (unsigned long)res._width*res._height, whd = wh*res._depth;
+        cimg_forXYZC(*this,x,y,z,c) res(c,z,y,x,wh,whd) = (t)*(ptrs++);
+      }
+      if (!res)
+        throw CImgArgumentException(_cimg_instance
+                                    "permute_axes() : Invalid specified permutation '%s'.",
+                                    cimg_instance,
+                                    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 *const order) {
+      return get_permute_axes(order).move_to(*this);
+    }
+
+    CImg<T> get_permute_axes(const char *const order) const {
+      const T foo = (T)0;
+      return _get_permute_axes(order,foo);
+    }
+
+    //! 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 = _spectrum = 1; break;
+      case 'y' : _height = siz; _width = _depth = _spectrum = 1; break;
+      case 'z' : _depth = siz; _width = _height = _spectrum = 1; break;
+      default : _spectrum = siz; _width = _height = _depth = 1;
+      }
+      return *this;
+    }
+
+    CImg<T> get_unroll(const char axis) const {
+      return (+*this).unroll(axis);
+    }
+
+    //! Rotate an image.
+    /**
+       \param angle = rotation angle (in degrees).
+       \param cond = rotation type. can be :
+       - 0 = zero-value at borders
+       - 1 = nearest pixel.
+       - 2 = cyclic.
+       \note Returned image will probably have a different size than the image instance *this.
+    **/
+    CImg<T>& rotate(const float angle, const unsigned int border_conditions=0, const unsigned int interpolation=1) {
+      return get_rotate(angle,border_conditions,interpolation).move_to(*this);
+    }
+
+    CImg<T> get_rotate(const float angle, const unsigned int border_conditions=0, const unsigned int interpolation=1) const {
+      if (is_empty()) return *this;
+      CImg<T> res;
+      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 = width() - 1, hm1 = height() - 1;
+        const int iangle = (int)nangle/90;
+        switch (iangle) {
+        case 1 : {
+          res.assign(_height,_width,_depth,_spectrum);
+          T *ptrd = res._data;
+          cimg_forXYZC(res,x,y,z,c) *(ptrd++) = (*this)(y,hm1-x,z,c);
+        } break;
+        case 2 : {
+          res.assign(_width,_height,_depth,_spectrum);
+          T *ptrd = res._data;
+          cimg_forXYZC(res,x,y,z,c) *(ptrd++) = (*this)(wm1-x,hm1-y,z,c);
+        } break;
+        case 3 : {
+          res.assign(_height,_width,_depth,_spectrum);
+          T *ptrd = res._data;
+          cimg_forXYZC(res,x,y,z,c) *(ptrd++) = (*this)(wm1-y,x,z,c);
+        } break;
+        default :
+          return *this;
+        }
+      } else { // generic version
+        const Tfloat vmin = (Tfloat)cimg::type<T>::min(), vmax = (Tfloat)cimg::type<T>::max();
+        const float
+          rad = (float)(nangle*cimg::PI/180.0),
+          ca = (float)std::cos(rad),
+          sa = (float)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);
+        res.assign((int)(ux+vx),(int)(uy+vy),_depth,_spectrum);
+        switch (border_conditions) {
+        case 0 : {
+          switch (interpolation) {
+          case 2 : {
+            cimg_forXY(res,x,y) cimg_forZC(*this,z,c) {
+              const Tfloat val = cubic_atXY(w2 + (x-dw2)*ca + (y-dh2)*sa,h2 - (x-dw2)*sa + (y-dh2)*ca,z,c,0);
+              res(x,y,z,c) = (T)(val<vmin?vmin:val>vmax?vmax:val);
+            }
+          } break;
+          case 1 : {
+            cimg_forXY(res,x,y) cimg_forZC(*this,z,c)
+              res(x,y,z,c) = (T)linear_atXY(w2 + (x-dw2)*ca + (y-dh2)*sa,h2 - (x-dw2)*sa + (y-dh2)*ca,z,c,0);
+          } break;
+          default : {
+            cimg_forXY(res,x,y) cimg_forZC(*this,z,c)
+              res(x,y,z,c) = atXY((int)(w2 + (x-dw2)*ca + (y-dh2)*sa),(int)(h2 - (x-dw2)*sa + (y-dh2)*ca),z,c,0);
+          }
+          }
+        } break;
+        case 1 : {
+          switch (interpolation) {
+          case 2 : {
+            cimg_forXY(res,x,y) cimg_forZC(*this,z,c) {
+              const Tfloat val = _cubic_atXY(w2 + (x-dw2)*ca + (y-dh2)*sa,h2 - (x-dw2)*sa + (y-dh2)*ca,z,c);
+              res(x,y,z,c) = (T)(val<vmin?vmin:val>vmax?vmax:val);
+            }
+          } break;
+          case 1 : {
+            cimg_forXY(res,x,y) cimg_forZC(*this,z,c)
+              res(x,y,z,c) = (T)_linear_atXY(w2 + (x-dw2)*ca + (y-dh2)*sa,h2 - (x-dw2)*sa + (y-dh2)*ca,z,c);
+          } break;
+          default : {
+            cimg_forXY(res,x,y) cimg_forZC(*this,z,c)
+              res(x,y,z,c) = _atXY((int)(w2 + (x-dw2)*ca + (y-dh2)*sa),(int)(h2 - (x-dw2)*sa + (y-dh2)*ca),z,c);
+          }
+          }
+        } break;
+        case 2 : {
+          switch (interpolation) {
+          case 2 : {
+            cimg_forXY(res,x,y) cimg_forZC(*this,z,c) {
+              const Tfloat val = _cubic_atXY(cimg::mod(w2 + (x-dw2)*ca + (y-dh2)*sa,(float)width()),
+                                             cimg::mod(h2 - (x-dw2)*sa + (y-dh2)*ca,(float)height()),z,c);
+              res(x,y,z,c) = (T)(val<vmin?vmin:val>vmax?vmax:val);
+            }
+          } break;
+          case 1 : {
+            cimg_forXY(res,x,y) cimg_forZC(*this,z,c)
+              res(x,y,z,c) = (T)_linear_atXY(cimg::mod(w2 + (x-dw2)*ca + (y-dh2)*sa,(float)width()),
+                                             cimg::mod(h2 - (x-dw2)*sa + (y-dh2)*ca,(float)height()),z,c);
+          } break;
+          default : {
+            cimg_forXY(res,x,y) cimg_forZC(*this,z,c)
+              res(x,y,z,c) = (*this)(cimg::mod((int)(w2 + (x-dw2)*ca + (y-dh2)*sa),width()),
+                                      cimg::mod((int)(h2 - (x-dw2)*sa + (y-dh2)*ca),height()),z,c);
+          }
+          }
+        } break;
+        default :
+          throw CImgArgumentException(_cimg_instance
+                                      "rotate() : Invalid specified border conditions %d "
+                                      "(should be { 0=dirichlet | 1=neumann | 2=cyclic }).",
+                                      cimg_instance,
+                                      border_conditions);
+        }
+      }
+      return res;
+    }
+
+    //! 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).move_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_instance
+                                    "rotate() : Invalid specified interpolation %d "
+                                    "(should be { 0=none | 1=linear | 2=bicubic }).",
+                                    cimg_instance,
+                                    interpolation);
+
+      if (is_empty()) return *this;
+      CImg<T> res(_width,_height,_depth,_spectrum);
+      const Tfloat vmin = (Tfloat)cimg::type<T>::min(), vmax = (Tfloat)cimg::type<T>::max();
+      const float
+        rad = (float)((angle*cimg::PI)/180.0),
+        ca = (float)std::cos(rad)/zoom,
+        sa = (float)std::sin(rad)/zoom;
+      switch (border_conditions) {
+      case 0 : {
+        switch (interpolation) {
+        case 2 : {
+          cimg_forXY(res,x,y) cimg_forZC(*this,z,c) {
+            const Tfloat val = cubic_atXY(cx + (x-cx)*ca + (y-cy)*sa,cy - (x-cx)*sa + (y-cy)*ca,z,c,0);
+            res(x,y,z,c) = (T)(val<vmin?vmin:val>vmax?vmax:val);
+          }
+        } break;
+        case 1 : {
+          cimg_forXY(res,x,y) cimg_forZC(*this,z,c)
+            res(x,y,z,c) = (T)linear_atXY(cx + (x-cx)*ca + (y-cy)*sa,cy - (x-cx)*sa + (y-cy)*ca,z,c,0);
+        } break;
+        default : {
+          cimg_forXY(res,x,y) cimg_forZC(*this,z,c)
+            res(x,y,z,c) = atXY((int)(cx + (x-cx)*ca + (y-cy)*sa),(int)(cy - (x-cx)*sa + (y-cy)*ca),z,c,0);
+        }
+        }
+      } break;
+      case 1 : {
+        switch (interpolation) {
+        case 2 : {
+          cimg_forXY(res,x,y) cimg_forZC(*this,z,c) {
+            const Tfloat val = _cubic_atXY(cx + (x-cx)*ca + (y-cy)*sa,cy - (x-cx)*sa + (y-cy)*ca,z,c);
+            res(x,y,z,c) = (T)(val<vmin?vmin:val>vmax?vmax:val);
+          }
+        } break;
+        case 1 : {
+          cimg_forXY(res,x,y) cimg_forZC(*this,z,c)
+            res(x,y,z,c) = (T)_linear_atXY(cx + (x-cx)*ca + (y-cy)*sa,cy - (x-cx)*sa + (y-cy)*ca,z,c);
+        } break;
+        default : {
+          cimg_forXY(res,x,y) cimg_forZC(*this,z,c)
+            res(x,y,z,c) = _atXY((int)(cx + (x-cx)*ca + (y-cy)*sa),(int)(cy - (x-cx)*sa + (y-cy)*ca),z,c);
+        }
+        }
+      } break;
+      case 2 : {
+        switch (interpolation) {
+        case 2 : {
+          cimg_forXY(res,x,y) cimg_forZC(*this,z,c) {
+            const Tfloat val = _cubic_atXY(cimg::mod(cx + (x-cx)*ca + (y-cy)*sa,(float)width()),
+                                           cimg::mod(cy - (x-cx)*sa + (y-cy)*ca,(float)height()),z,c);
+            res(x,y,z,c) = (T)(val<vmin?vmin:val>vmax?vmax:val);
+          }
+        } break;
+        case 1 : {
+          cimg_forXY(res,x,y) cimg_forZC(*this,z,c)
+            res(x,y,z,c) = (T)_linear_atXY(cimg::mod(cx + (x-cx)*ca + (y-cy)*sa,(float)width()),
+                                           cimg::mod(cy - (x-cx)*sa + (y-cy)*ca,(float)height()),z,c);
+        } break;
+        default : {
+          cimg_forXY(res,x,y) cimg_forZC(*this,z,c)
+            res(x,y,z,c) = (*this)(cimg::mod((int)(cx + (x-cx)*ca + (y-cy)*sa),width()),
+                                    cimg::mod((int)(cy - (x-cx)*sa + (y-cy)*ca),height()),z,c);
+        }
+        }
+      } break;
+      default :
+        throw CImgArgumentException(_cimg_instance
+                                    "rotate() : Invalid specified border conditions %d "
+                                    "(should be { 0=dirichlet | 1=neumann | 2=cyclic }).",
+                                    cimg_instance,
+                                    border_conditions);
+      }
+      return res;
+    }
+
+    //! Warp an image.
+    template<typename t>
+    CImg<T>& warp(const CImg<t>& warp, const bool is_relative=false,
+                  const bool interpolation=true, const unsigned int border_conditions=0) {
+      return get_warp(warp,is_relative,interpolation,border_conditions).move_to(*this);
+    }
+
+    template<typename t>
+    CImg<T> get_warp(const CImg<t>& warp, const bool is_relative=false,
+                     const bool interpolation=true, const unsigned int border_conditions=0) const {
+      if (is_empty() || !warp) return *this;
+      if (is_relative && !is_sameXYZ(warp))
+        throw CImgArgumentException(_cimg_instance
+                                    "warp() : Instance and specified relative warping field (%u,%u,%u,%u,%p) "
+                                    "have different XYZ dimensions.",
+                                    cimg_instance,
+                                    warp._width,warp._height,warp._depth,warp._spectrum,warp._data);
+
+      CImg<T> res(warp._width,warp._height,warp._depth,_spectrum);
+      T *ptrd = res._data;
+      switch (warp._spectrum) {
+      case 1 : // 1d warping.
+        if (is_relative) { // Relative warp coordinates
+          if (interpolation) switch (border_conditions) {
+            case 2 : {
+              cimg_forC(res,c) {
+                const t *ptrs0 = warp._data;
+                cimg_forXYZ(res,x,y,z)
+                  *(ptrd++) = (T)_linear_atX(cimg::mod(x - (float)*(ptrs0++),(float)_width),y,z,c);
+              }
+            } break;
+            case 1 : {
+              cimg_forC(res,c) {
+                const t *ptrs0 = warp._data;
+                cimg_forXYZ(res,x,y,z)
+                  *(ptrd++) = (T)_linear_atX(x - (float)*(ptrs0++),y,z,c);
+              }
+            } break;
+            default : {
+              cimg_forC(res,c) {
+                const t *ptrs0 = warp._data;
+                cimg_forXYZ(res,x,y,z)
+                  *(ptrd++) = (T)linear_atX(x - (float)*(ptrs0++),y,z,c,0);
+              }
+            }
+            } else switch (border_conditions) {
+            case 2 : {
+              cimg_forC(res,c) {
+                const t *ptrs0 = warp._data;
+                cimg_forXYZ(res,x,y,z)
+                  *(ptrd++) = (*this)(cimg::mod(x - (int)*(ptrs0++),(int)_width),y,z,c);
+              }
+            } break;
+            case 1 : {
+              cimg_forC(res,c) {
+                const t *ptrs0 = warp._data;
+                cimg_forXYZ(res,x,y,z)
+                  *(ptrd++) = _atX(x - (int)*(ptrs0++),y,z,c);
+              }
+            } break;
+            default : {
+              cimg_forC(res,c) {
+                const t *ptrs0 = warp._data;
+                cimg_forXYZ(res,x,y,z)
+                  *(ptrd++) = atX(x - (int)*(ptrs0++),y,z,c,0);
+              }
+            }
+            }
+        } else { // Absolute warp coordinates
+          if (interpolation) switch (border_conditions) {
+            case 2 : {
+              cimg_forC(res,c) {
+                const t *ptrs0 = warp._data;
+                cimg_forXYZ(res,x,y,z)
+                  *(ptrd++) = (T)_linear_atX(cimg::mod((float)*(ptrs0++),(float)_width),0,0,c);
+              }
+            } break;
+            case 1 : {
+              cimg_forC(res,c) {
+                const t *ptrs0 = warp._data;
+                cimg_forXYZ(res,x,y,z)
+                  *(ptrd++) = (T)_linear_atX((float)*(ptrs0++),0,0,c);
+              }
+            } break;
+            default : {
+              cimg_forC(res,c) {
+                const t *ptrs0 = warp._data;
+                cimg_forXYZ(res,x,y,z)
+                  *(ptrd++) = (T)linear_atX((float)*(ptrs0++),0,0,c,0);
+              }
+            }
+            } else switch (border_conditions) {
+            case 2 : {
+              cimg_forC(res,c) {
+                const t *ptrs0 = warp._data;
+                cimg_forXYZ(res,x,y,z)
+                  *(ptrd++) = (*this)(cimg::mod((int)*(ptrs0++),(int)_width),0,0,c);
+              }
+            } break;
+            case 1 : {
+              cimg_forC(res,c) {
+                const t *ptrs0 = warp._data;
+                cimg_forXYZ(res,x,y,z)
+                  *(ptrd++) = _atX((int)*(ptrs0++),0,0,c);
+              }
+            } break;
+            default : {
+              cimg_forC(res,c) {
+                const t *ptrs0 = warp._data;
+                cimg_forXYZ(res,x,y,z)
+                  *(ptrd++) = atX((int)*(ptrs0++),0,0,c,0);
+              }
+            }
+            }
+        }
+        break;
+
+      case 2 : // 2d warping
+        if (is_relative) { // Relative warp coordinates
+          if (interpolation) switch (border_conditions) {
+            case 2 : {
+              cimg_forC(res,c) {
+                const t *ptrs0 = warp.data(0,0,0,0), *ptrs1 = warp.data(0,0,0,1);
+                cimg_forXYZ(res,x,y,z)
+                  *(ptrd++) = (T)_linear_atXY(cimg::mod(x - (float)*(ptrs0++),(float)_width),
+                                              cimg::mod(y - (float)*(ptrs1++),(float)_height),z,c);
+              }
+            } break;
+            case 1 : {
+              cimg_forC(res,c) {
+                const t *ptrs0 = warp.data(0,0,0,0), *ptrs1 = warp.data(0,0,0,1);
+                cimg_forXYZ(res,x,y,z)
+                  *(ptrd++) = (T)_linear_atXY(x - (float)*(ptrs0++),y - (float)*(ptrs1++),z,c);
+              }
+            } break;
+            default : {
+              cimg_forC(res,c) {
+                const t *ptrs0 = warp.data(0,0,0,0), *ptrs1 = warp.data(0,0,0,1);
+                cimg_forXYZ(res,x,y,z)
+                  *(ptrd++) = (T)linear_atXY(x - (float)*(ptrs0++),y - (float)*(ptrs1++),z,c,0);
+              }
+            }
+            } else switch (border_conditions) {
+            case 2 : {
+              cimg_forC(res,c) {
+                const t *ptrs0 = warp.data(0,0,0,0), *ptrs1 = warp.data(0,0,0,1);
+                cimg_forXYZ(res,x,y,z)
+                  *(ptrd++) = (*this)(cimg::mod(x - (int)*(ptrs0++),(int)_width),
+                                      cimg::mod(y - (int)*(ptrs1++),(int)_height),z,c);
+              }
+            } break;
+            case 1 : {
+              cimg_forC(res,c) {
+                const t *ptrs0 = warp.data(0,0,0,0), *ptrs1 = warp.data(0,0,0,1);
+                cimg_forXYZ(res,x,y,z)
+                  *(ptrd++) = _atXY(x - (int)*(ptrs0++),y - (int)*(ptrs1++),z,c);
+              }
+            } break;
+            default : {
+              cimg_forC(res,c) {
+                const t *ptrs0 = warp.data(0,0,0,0), *ptrs1 = warp.data(0,0,0,1);
+                cimg_forXYZ(res,x,y,z)
+                  *(ptrd++) = atXY(x - (int)*(ptrs0++),y - (int)*(ptrs1++),z,c,0);
+              }
+            }
+            }
+        } else { // Absolute warp coordinates
+          if (interpolation) switch (border_conditions) {
+            case 2 : {
+              cimg_forC(res,c) {
+                const t *ptrs0 = warp.data(0,0,0,0), *ptrs1 = warp.data(0,0,0,1);
+                cimg_forXYZ(res,x,y,z)
+                  *(ptrd++) = (T)_linear_atXY(cimg::mod((float)*(ptrs0++),(float)_width),
+                                              cimg::mod((float)*(ptrs1++),(float)_height),0,c);
+              }
+            } break;
+            case 1 : {
+              cimg_forC(res,c) {
+                const t *ptrs0 = warp.data(0,0,0,0), *ptrs1 = warp.data(0,0,0,1);
+                cimg_forXYZ(res,x,y,z)
+                  *(ptrd++) = (T)_linear_atXY((float)*(ptrs0++),(float)*(ptrs1++),0,c);
+              }
+            } break;
+            default : {
+              cimg_forC(res,c) {
+                const t *ptrs0 = warp.data(0,0,0,0), *ptrs1 = warp.data(0,0,0,1);
+                cimg_forXYZ(res,x,y,z)
+                  *(ptrd++) = (T)linear_atXY((float)*(ptrs0++),(float)*(ptrs1++),0,c,0);
+              }
+            }
+            } else switch (border_conditions) {
+            case 2 : {
+              cimg_forC(res,c) {
+                const t *ptrs0 = warp.data(0,0,0,0), *ptrs1 = warp.data(0,0,0,1);
+                cimg_forXYZ(res,x,y,z)
+                  *(ptrd++) = (*this)(cimg::mod((int)*(ptrs0++),(int)_width),
+                                      cimg::mod((int)*(ptrs1++),(int)_height),0,c);
+              }
+            } break;
+            case 1 : {
+              cimg_forC(res,c) {
+                const t *ptrs0 = warp.data(0,0,0,0), *ptrs1 = warp.data(0,0,0,1);
+                cimg_forXYZ(res,x,y,z)
+                  *(ptrd++) = _atXY((int)*(ptrs0++),(int)*(ptrs1++),0,c);
+              }
+            } break;
+            default : {
+              cimg_forC(res,c) {
+                const t *ptrs0 = warp.data(0,0,0,0), *ptrs1 = warp.data(0,0,0,1);
+                cimg_forXYZ(res,x,y,z)
+                  *(ptrd++) = atXY((int)*(ptrs0++),(int)*(ptrs1++),0,c,0);
+              }
+            }
+            }
+        }
+        break;
+
+      default : // 3d warping
+        if (is_relative) { // Relative warp coordinates
+          if (interpolation) switch (border_conditions) {
+            case 2 : {
+              cimg_forC(res,c) {
+                const t *ptrs0 = warp.data(0,0,0,0), *ptrs1 = warp.data(0,0,0,1), *ptrs2 = warp.data(0,0,0,2);
+                cimg_forXYZ(res,x,y,z)
+                  *(ptrd++) = (T)_linear_atXYZ(cimg::mod(x - (float)*(ptrs0++),(float)_width),
+                                               cimg::mod(y - (float)*(ptrs1++),(float)_height),
+                                               cimg::mod(z - (float)*(ptrs2++),(float)_depth),c);
+              }
+            } break;
+            case 1 : {
+              cimg_forC(res,c) {
+                const t *ptrs0 = warp.data(0,0,0,0), *ptrs1 = warp.data(0,0,0,1), *ptrs2 = warp.data(0,0,0,2);
+                cimg_forXYZ(res,x,y,z)
+                  *(ptrd++) = (T)_linear_atXYZ(x - (float)*(ptrs0++),y - (float)*(ptrs1++),z - (float)*(ptrs2++),c);
+              }
+            } break;
+            default : {
+              cimg_forC(res,c) {
+                const t *ptrs0 = warp.data(0,0,0,0), *ptrs1 = warp.data(0,0,0,1), *ptrs2 = warp.data(0,0,0,2);
+                cimg_forXYZ(res,x,y,z)
+                  *(ptrd++) = (T)linear_atXYZ(x - (float)*(ptrs0++),y - (float)*(ptrs1++),z - (float)*(ptrs2++),c,0);
+              }
+            }
+            } else switch (border_conditions) {
+            case 2 : {
+              cimg_forC(res,c) {
+                const t *ptrs0 = warp.data(0,0,0,0), *ptrs1 = warp.data(0,0,0,1), *ptrs2 = warp.data(0,0,0,2);
+                cimg_forXYZ(res,x,y,z)
+                  *(ptrd++) = (*this)(cimg::mod(x - (int)*(ptrs0++),(int)_width),
+                                      cimg::mod(y - (int)*(ptrs1++),(int)_height),
+                                      cimg::mod(z - (int)*(ptrs2++),(int)_depth),c);
+              }
+            } break;
+            case 1 : {
+              cimg_forC(res,c) {
+                const t *ptrs0 = warp.data(0,0,0,0), *ptrs1 = warp.data(0,0,0,1), *ptrs2 = warp.data(0,0,0,2);
+                cimg_forXYZ(res,x,y,z)
+                  *(ptrd++) = _atXYZ(x - (int)*(ptrs0++),y - (int)*(ptrs1++),z - (int)*(ptrs2++),c);
+              }
+            } break;
+            default : {
+              cimg_forC(res,c) {
+                const t *ptrs0 = warp.data(0,0,0,0), *ptrs1 = warp.data(0,0,0,1), *ptrs2 = warp.data(0,0,0,2);
+                cimg_forXYZ(res,x,y,z)
+                  *(ptrd++) = atXYZ(x - (int)*(ptrs0++),y - (int)*(ptrs1++),z - (int)*(ptrs2++),c,0);
+              }
+            }
+            }
+        } else { // Absolute warp coordinates
+          if (interpolation) switch (border_conditions) {
+            case 2 : {
+              cimg_forC(res,c) {
+                const t *ptrs0 = warp.data(0,0,0,0), *ptrs1 = warp.data(0,0,0,1), *ptrs2 = warp.data(0,0,0,2);
+                cimg_forXYZ(res,x,y,z)
+                  *(ptrd++) = (T)_linear_atXYZ(cimg::mod((float)*(ptrs0++),(float)_width),
+                                               cimg::mod((float)*(ptrs1++),(float)_height),
+                                               cimg::mod((float)*(ptrs2++),(float)_depth),c);
+              }
+            } break;
+            case 1 : {
+              cimg_forC(res,c) {
+                const t *ptrs0 = warp.data(0,0,0,0), *ptrs1 = warp.data(0,0,0,1), *ptrs2 = warp.data(0,0,0,2);
+                cimg_forXYZ(res,x,y,z)
+                  *(ptrd++) = (T)_linear_atXYZ((float)*(ptrs0++),(float)*(ptrs1++),(float)*(ptrs2++),c);
+              }
+            } break;
+            default : {
+              cimg_forC(res,c) {
+                const t *ptrs0 = warp.data(0,0,0,0), *ptrs1 = warp.data(0,0,0,1), *ptrs2 = warp.data(0,0,0,2);
+                cimg_forXYZ(res,x,y,z)
+                  *(ptrd++) = (T)linear_atXYZ((float)*(ptrs0++),(float)*(ptrs1++),(float)*(ptrs2++),c,0);
+              }
+            }
+            } else switch (border_conditions) {
+            case 2 : {
+              cimg_forC(res,c) {
+                const t *ptrs0 = warp.data(0,0,0,0), *ptrs1 = warp.data(0,0,0,1), *ptrs2 = warp.data(0,0,0,2);
+                cimg_forXYZ(res,x,y,z)
+                  *(ptrd++) = (*this)(cimg::mod((int)*(ptrs0++),(int)_width),
+                                      cimg::mod((int)*(ptrs1++),(int)_height),
+                                      cimg::mod((int)*(ptrs2++),(int)_depth),c);
+              }
+            } break;
+            case 1 : {
+              cimg_forC(res,c) {
+                const t *ptrs0 = warp.data(0,0,0,0), *ptrs1 = warp.data(0,0,0,1), *ptrs2 = warp.data(0,0,0,2);
+                cimg_forXYZ(res,x,y,z)
+                  *(ptrd++) = _atXYZ((int)*(ptrs0++),(int)*(ptrs1++),(int)*(ptrs2++),c);
+              }
+            } break;
+            default : {
+              cimg_forC(res,c) {
+                const t *ptrs0 = warp.data(0,0,0,0), *ptrs1 = warp.data(0,0,0,1), *ptrs2 = warp.data(0,0,0,2);
+                cimg_forXYZ(res,x,y,z)
+                  *(ptrd++) = atXYZ((int)*(ptrs0++),(int)*(ptrs1++),(int)*(ptrs2++),c,0);
+              }
+            }
+            }
+        }
+      }
+      return res;
+    }
+
+    //! 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) {
+      if (_depth<2) return *this;
+      return get_projections2d(x0,y0,z0).move_to(*this);
+    }
+
+    CImg<T> get_projections2d(const unsigned int x0, const unsigned int y0, const unsigned int z0) const {
+      if (is_empty() || _depth<2) return +*this;
+      const unsigned int
+        _x0 = (x0>=_width)?_width - 1:x0,
+        _y0 = (y0>=_height)?_height - 1:y0,
+        _z0 = (z0>=_depth)?_depth - 1:z0;
+      const CImg<T>
+        img_xy = get_crop(0,0,_z0,0,_width-1,_height-1,_z0,_spectrum-1),
+        img_zy = get_crop(_x0,0,0,0,_x0,_height-1,_depth-1,_spectrum-1).permute_axes("xzyc").resize(_depth,_height,1,-100,-1),
+        img_xz = get_crop(0,_y0,0,0,_width-1,_y0,_depth-1,_spectrum-1).resize(_width,_depth,1,-100,-1);
+      return CImg<T>(_width + _depth,_height + _depth,1,_spectrum,cimg::min(img_xy.min(),img_zy.min(),img_xz.min())).
+        draw_image(0,0,img_xy).draw_image(img_xy._width,0,img_zy).
+        draw_image(0,img_xy._height,img_xz);
+    }
+
+    //! Return 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 c0 = C-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 c1 = C-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 c0,
+                  const int x1, const int y1, const int z1, const int c1,
+                  const bool border_condition=false) {
+      return get_crop(x0,y0,z0,c0,x1,y1,z1,c1,border_condition).move_to(*this);
+    }
+
+    CImg<T> get_crop(const int x0, const int y0, const int z0, const int c0,
+                     const int x1, const int y1, const int z1, const int c1,
+                     const bool border_condition=false) const {
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "crop() : Empty instance.",
+                                    cimg_instance);
+      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,
+        nc0 = c0<c1?c0:c1, nc1 = c0^c1^nc0;
+      CImg<T> res(1U + nx1 - nx0,1U + ny1 - ny0,1U + nz1 - nz0,1U + nc1 - nc0);
+      if (nx0<0 || nx1>=width() || ny0<0 || ny1>=height() || nz0<0 || nz1>=depth() || nc0<0 || nc1>=spectrum()) {
+        if (border_condition) cimg_forXYZC(res,x,y,z,c) res(x,y,z,c) = _atXYZC(nx0+x,ny0+y,nz0+z,nc0+c);
+        else res.fill(0).draw_image(-nx0,-ny0,-nz0,-nc0,*this);
+      } else res.draw_image(-nx0,-ny0,-nz0,-nc0,*this);
+      return res;
+    }
+
+    //! Return a rectangular part of the image instance.
+    /**
+       \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,_spectrum-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,_spectrum-1,border_condition);
+    }
+
+    //! Return a rectangular part of the image instance.
+    /**
+       \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,_spectrum - 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,_spectrum - 1,border_condition);
+    }
+
+    //! Return a rectangular part of the image instance.
+    /**
+       \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,_spectrum-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,_spectrum-1,border_condition);
+    }
+
+    //! Autocrop an image, regarding of the specified backround value.
+    CImg<T>& autocrop(const T value, const char *const axes="czyx") {
+      if (is_empty()) return *this;
+      for (const char *s = axes; *s; ++s) {
+        const char axis = cimg::uncase(*s);
+        const CImg<intT> coords = _autocrop(value,axis);
+        if (coords[0]==-1 && coords[1]==-1) return assign(); // Image has only 'value' pixels.
+        else switch (axis) {
+        case 'x' : {
+          const int x0 = coords[0], x1 = coords[1];
+          if (x0>=0 && x1>=0) crop(x0,x1);
+        } break;
+        case 'y' : {
+          const int y0 = coords[0], y1 = coords[1];
+          if (y0>=0 && y1>=0) crop(0,y0,_width-1,y1);
+        } break;
+        case 'z' : {
+          const int z0 = coords[0], z1 = coords[1];
+          if (z0>=0 && z1>=0) crop(0,0,z0,_width-1,_height-1,z1);
+        } break;
+        default : {
+          const int c0 = coords[0], c1 = coords[1];
+          if (c0>=0 && c1>=0) crop(0,0,0,c0,_width-1,_height-1,_depth-1,c1);
+        }
+        }
+      }
+      return *this;
+    }
+
+    CImg<T> get_autocrop(const T value, const char *const axes="czyx") 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;
+      for (const char *s = axes; *s; ++s) {
+        const char axis = cimg::uncase(*s);
+        switch (axis) {
+        case 'x' : {
+          int x0 = width(), x1 = -1;
+          cimg_forC(*this,c) {
+            const CImg<intT> coords = get_shared_channel(c)._autocrop(color[c],'x');
+            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==width() && x1==-1) return assign(); else crop(x0,x1);
+        } break;
+        case 'y' : {
+          int y0 = height(), y1 = -1;
+          cimg_forC(*this,c) {
+            const CImg<intT> coords = get_shared_channel(c)._autocrop(color[c],'y');
+            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==height() && y1==-1) return assign(); else crop(0,y0,_width-1,y1);
+        } break;
+        default : {
+          int z0 = depth(), z1 = -1;
+          cimg_forC(*this,c) {
+            const CImg<intT> coords = get_shared_channel(c)._autocrop(color[c],'z');
+            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==depth() && z1==-1) return assign(); else crop(0,0,z0,_width-1,_height-1,z1);
+        }
+        }
+      }
+      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).move_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);
+    }
+
+    CImg<intT> _autocrop(const T value, const char axis) const {
+      CImg<intT> res;
+      switch (cimg::uncase(axis)) {
+      case 'x' : {
+        int x0 = -1, x1 = -1;
+        cimg_forX(*this,x) cimg_forYZC(*this,y,z,c)
+          if ((*this)(x,y,z,c)!=value) { x0 = x; x = width(); y = height(); z = depth(); c = spectrum(); }
+        if (x0>=0) {
+          for (int x = width()-1; x>=0; --x) cimg_forYZC(*this,y,z,c)
+            if ((*this)(x,y,z,c)!=value) { x1 = x; x = 0; y = height(); z = depth(); c = spectrum(); }
+        }
+        res = CImg<intT>::vector(x0,x1);
+      } break;
+      case 'y' : {
+        int y0 = -1, y1 = -1;
+        cimg_forY(*this,y) cimg_forXZC(*this,x,z,c)
+          if ((*this)(x,y,z,c)!=value) { y0 = y; x = width(); y = height(); z = depth(); c = spectrum(); }
+        if (y0>=0) {
+          for (int y = height()-1; y>=0; --y) cimg_forXZC(*this,x,z,c)
+            if ((*this)(x,y,z,c)!=value) { y1 = y; x = width(); y = 0; z = depth(); c = spectrum(); }
+        }
+        res = CImg<intT>::vector(y0,y1);
+      } break;
+      case 'z' : {
+        int z0 = -1, z1 = -1;
+        cimg_forZ(*this,z) cimg_forXYC(*this,x,y,c)
+          if ((*this)(x,y,z,c)!=value) { z0 = z; x = width(); y = height(); z = depth(); c = spectrum(); }
+        if (z0>=0) {
+          for (int z = depth()-1; z>=0; --z) cimg_forXYC(*this,x,y,c)
+            if ((*this)(x,y,z,c)!=value) { z1 = z; x = width(); y = height(); z = 0; c = spectrum(); }
+        }
+        res = CImg<intT>::vector(z0,z1);
+      } break;
+      default : {
+        int c0 = -1, c1 = -1;
+        cimg_forC(*this,c) cimg_forXYZ(*this,x,y,z)
+          if ((*this)(x,y,z,c)!=value) { c0 = c; x = width(); y = height(); z = depth(); c = spectrum(); }
+        if (c0>=0) {
+          for (int c = spectrum()-1; c>=0; --c) cimg_forXYZ(*this,x,y,z)
+            if ((*this)(x,y,z,c)!=value) { c1 = c; x = width(); y = height(); z = depth(); c = 0; }
+        }
+        res = CImg<intT>::vector(c0,c1);
+      }
+      }
+      return res;
+    }
+
+    //! Return one column.
+    CImg<T>& column(const int x0) {
+      return columns(x0,x0);
+    }
+
+    CImg<T> get_column(const int x0) const {
+      return get_columns(x0,x0);
+    }
+
+    //! Return a set of columns.
+    CImg<T>& columns(const int x0, const int x1) {
+      return get_columns(x0,x1).move_to(*this);
+    }
+
+    CImg<T> get_columns(const int x0, const int x1) const {
+      return get_crop(x0,0,0,0,x1,height()-1,depth()-1,spectrum()-1);
+    }
+
+    //! Return a line.
+    CImg<T>& line(const int y0) {
+      return lines(y0,y0);
+    }
+
+    CImg<T> get_line(const int y0) const {
+      return get_lines(y0,y0);
+    }
+
+    //! Return a set of lines.
+    CImg<T>& lines(const int y0, const int y1) {
+      return get_lines(y0,y1).move_to(*this);
+    }
+
+    CImg<T> get_lines(const int y0, const int y1) const {
+      return get_crop(0,y0,0,0,width()-1,y1,depth()-1,spectrum()-1);
+    }
+
+    //! Return a slice.
+    CImg<T>& slice(const int z0) {
+      return slices(z0,z0);
+    }
+
+    CImg<T> get_slice(const int z0) const {
+      return get_slices(z0,z0);
+    }
+
+    //! Return a set of slices.
+    CImg<T>& slices(const int z0, const int z1) {
+      return get_slices(z0,z1).move_to(*this);
+    }
+
+    CImg<T> get_slices(const int z0, const int z1) const {
+      return get_crop(0,0,z0,0,width()-1,height()-1,z1,spectrum()-1);
+    }
+
+    //! Return a channel.
+    CImg<T>& channel(const int c0) {
+      return channels(c0,c0);
+    }
+
+    CImg<T> get_channel(const int c0) const {
+      return get_channels(c0,c0);
+    }
+
+    //! Return a set of channels.
+    CImg<T>& channels(const int c0, const int c1) {
+      return get_channels(c0,c1).move_to(*this);
+    }
+
+    CImg<T> get_channels(const int c0, const int c1) const {
+      return get_crop(0,0,0,c0,width()-1,height()-1,depth()-1,c1);
+    }
+
+    //! Return a shared-memory image referencing a set of points of the image instance.
+    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 c0=0) {
+      const unsigned int beg = (unsigned int)offset(x0,y0,z0,c0), end = offset(x1,y0,z0,c0);
+      if (beg>end || beg>=size() || end>=size())
+        throw CImgArgumentException(_cimg_instance
+                                    "get_shared_points() : Invalid request of a shared-memory subset (%u->%u,%u,%u,%u).",
+                                    cimg_instance,
+                                    x0,x1,y0,z0,c0);
+
+      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 c0=0) const {
+      const unsigned int beg = (unsigned int)offset(x0,y0,z0,c0), end = offset(x1,y0,z0,c0);
+      if (beg>end || beg>=size() || end>=size())
+        throw CImgArgumentException(_cimg_instance
+                                    "get_shared_points() : Invalid request of a shared-memory subset (%u->%u,%u,%u,%u).",
+                                    cimg_instance,
+                                    x0,x1,y0,z0,c0);
+
+      return CImg<T>(_data+beg,x1-x0+1,1,1,1,true);
+    }
+
+    //! Return a shared-memory image referencing a set of lines of the image instance.
+    CImg<T> get_shared_lines(const unsigned int y0, const unsigned int y1,
+                             const unsigned int z0=0, const unsigned int c0=0) {
+      const unsigned int beg = offset(0,y0,z0,c0), end = offset(0,y1,z0,c0);
+      if (beg>end || beg>=size() || end>=size())
+        throw CImgArgumentException(_cimg_instance
+                                    "get_shared_lines() : Invalid request of a shared-memory subset (0->%u,%u->%u,%u,%u).",
+                                    cimg_instance,
+                                    _width-1,y0,y1,z0,c0);
+
+      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 c0=0) const {
+      const unsigned int beg = offset(0,y0,z0,c0), end = offset(0,y1,z0,c0);
+      if (beg>end || beg>=size() || end>=size())
+        throw CImgArgumentException(_cimg_instance
+                                    "get_shared_lines() : Invalid request of a shared-memory subset (0->%u,%u->%u,%u,%u).",
+                                    cimg_instance,
+                                    _width-1,y0,y1,z0,c0);
+
+      return CImg<T>(_data+beg,_width,y1-y0+1,1,1,true);
+    }
+
+    //! Return a shared-memory image referencing one particular line (y0,z0,c0) of the image instance.
+    CImg<T> get_shared_line(const unsigned int y0, const unsigned int z0=0, const unsigned int c0=0) {
+      return get_shared_lines(y0,y0,z0,c0);
+    }
+
+    const CImg<T> get_shared_line(const unsigned int y0, const unsigned int z0=0, const unsigned int c0=0) const {
+      return get_shared_lines(y0,y0,z0,c0);
+    }
+
+    //! Return a shared memory image referencing a set of planes (z0->z1,c0) of the image instance.
+    CImg<T> get_shared_planes(const unsigned int z0, const unsigned int z1, const unsigned int c0=0) {
+      const unsigned int beg = offset(0,0,z0,c0), end = offset(0,0,z1,c0);
+      if (beg>end || beg>=size() || end>=size())
+        throw CImgArgumentException(_cimg_instance
+                                    "get_shared_planes() : Invalid request of a shared-memory subset (0->%u,0->%u,%u->%u,%u).",
+                                    cimg_instance,
+                                    _width-1,_height-1,z0,z1,c0);
+
+      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 c0=0) const {
+      const unsigned int beg = offset(0,0,z0,c0), end = offset(0,0,z1,c0);
+      if (beg>end || beg>=size() || end>=size())
+        throw CImgArgumentException(_cimg_instance
+                                    "get_shared_planes() : Invalid request of a shared-memory subset (0->%u,0->%u,%u->%u,%u).",
+                                    cimg_instance,
+                                    _width-1,_height-1,z0,z1,c0);
+
+      return CImg<T>(_data+beg,_width,_height,z1-z0+1,1,true);
+    }
+
+    //! Return a shared-memory image referencing one plane (z0,c0) of the image instance.
+    CImg<T> get_shared_plane(const unsigned int z0, const unsigned int c0=0) {
+      return get_shared_planes(z0,z0,c0);
+    }
+
+    const CImg<T> get_shared_plane(const unsigned int z0, const unsigned int c0=0) const {
+      return get_shared_planes(z0,z0,c0);
+    }
+
+    //! Return a shared-memory image referencing a set of channels (c0->c1) of the image instance.
+    CImg<T> get_shared_channels(const unsigned int c0, const unsigned int c1) {
+      const unsigned int beg = offset(0,0,0,c0), end = offset(0,0,0,c1);
+      if (beg>end || beg>=size() || end>=size())
+        throw CImgArgumentException(_cimg_instance
+                                    "get_shared_channels() : Invalid request of a shared-memory subset (0->%u,0->%u,0->%u,%u->%u).",
+                                    cimg_instance,
+                                    _width-1,_height-1,_depth-1,c0,c1);
+
+      return CImg<T>(_data+beg,_width,_height,_depth,c1-c0+1,true);
+    }
+
+    const CImg<T> get_shared_channels(const unsigned int c0, const unsigned int c1) const {
+      const unsigned int beg = offset(0,0,0,c0), end = offset(0,0,0,c1);
+      if (beg>end || beg>=size() || end>=size())
+        throw CImgArgumentException(_cimg_instance
+                                    "get_shared_channels() : Invalid request of a shared-memory subset (0->%u,0->%u,0->%u,%u->%u).",
+                                    cimg_instance,
+                                    _width-1,_height-1,_depth-1,c0,c1);
+
+      return CImg<T>(_data+beg,_width,_height,_depth,c1-c0+1,true);
+    }
+
+    //! Return a shared-memory image referencing one channel c0 of the image instance.
+    CImg<T> get_shared_channel(const unsigned int c0) {
+      return get_shared_channels(c0,c0);
+    }
+
+    const CImg<T> get_shared_channel(const unsigned int c0) const {
+      return get_shared_channels(c0,c0);
+    }
+
+    //! Return a shared version of the image instance.
+    CImg<T> get_shared() {
+      return CImg<T>(_data,_width,_height,_depth,_spectrum,true);
+    }
+
+    const CImg<T> get_shared() const {
+      return CImg<T>(_data,_width,_height,_depth,_spectrum,true);
+    }
+
+    //! Split image into a list.
+    CImgList<T> get_split(const char axis, const int nb=0) const {
+      CImgList<T> res;
+      const char _axis = cimg::uncase(axis);
+
+      if (nb<=0) { // Split by bloc size.
+        const unsigned int dp = (unsigned int)(nb?-nb:1);
+        int p = 0;
+        switch (_axis) {
+        case 'x': {
+          for (int pe=_width-dp; p<pe; p+=dp) get_crop(p,0,0,0,p+dp-1,_height-1,_depth-1,_spectrum-1).move_to(res);
+          get_crop(p,0,0,0,_width-1,_height-1,_depth-1,_spectrum-1).move_to(res);
+        } break;
+        case 'y': {
+          for (int pe=_height-dp; p<pe; p+=dp) get_crop(0,p,0,0,_width-1,p+dp-1,_depth-1,_spectrum-1).move_to(res);
+          get_crop(0,p,0,0,_width-1,_height-1,_depth-1,_spectrum-1).move_to(res);
+        } break;
+        case 'z': {
+          for (int pe=_depth-dp; p<pe; p+=dp) get_crop(0,0,p,0,_width-1,_height-1,p+dp-1,_spectrum-1).move_to(res);
+          get_crop(0,0,p,0,_width-1,_height-1,_depth-1,_spectrum-1).move_to(res);
+        } break;
+        default: {
+          for (int pe=_spectrum-dp; p<pe; p+=dp) get_crop(0,0,0,p,_width-1,_height-1,_depth-1,p+dp-1).move_to(res);
+          get_crop(0,0,0,p,_width-1,_height-1,_depth-1,_spectrum-1).move_to(res);
+        }
+        }
+
+      } else { // Split by number of (non-homogeneous) blocs.
+        const unsigned int siz = _axis=='x'?_width:_axis=='y'?_height:_axis=='z'?_depth:_axis=='c'?_spectrum:0;
+        if ((unsigned int)nb>siz)
+          throw CImgArgumentException(_cimg_instance
+                                      "get_split() : Instance cannot be split along %c-axis into %u blocs.",
+                                      cimg_instance,
+                                      axis,nb);
+        int err = (int)siz;
+        unsigned int _p = 0;
+        switch (_axis) {
+        case 'x' : {
+          cimg_forX(*this,p) if ((err-=nb)<=0) {
+            get_crop(_p,0,0,0,p,_height-1,_depth-1,_spectrum-1).move_to(res);
+            err+=(int)siz;
+            _p=p+1;
+          }
+        } break;
+        case 'y' : {
+          cimg_forY(*this,p) if ((err-=nb)<=0) {
+            get_crop(0,_p,0,0,_width-1,p,_depth-1,_spectrum-1).move_to(res);
+            err+=(int)siz;
+            _p=p+1;
+          }
+        } break;
+        case 'z' : {
+          cimg_forZ(*this,p) if ((err-=nb)<=0) {
+            get_crop(0,0,_p,0,_width-1,_height-1,p,_spectrum-1).move_to(res);
+            err+=(int)siz;
+            _p=p+1;
+          }
+        } break;
+        default : {
+          cimg_forC(*this,p) if ((err-=nb)<=0) {
+            get_crop(0,0,0,_p,_width-1,_height-1,_depth-1,p).move_to(res);
+            err+=(int)siz;
+            _p=p+1;
+          }
+        }
+        }
+      }
+      return res;
+    }
+
+    //! Split image into a list of one-column vectors, according to specified splitting value.
+    CImgList<T> get_split(const T value, const bool keep_values, const bool is_shared) const {
+      CImgList<T> res;
+      for (const T *ps = _data, *_ps = ps, *const pe = end(); ps<pe; ) {
+        while (_ps<pe && *_ps==value) ++_ps;
+        unsigned int siz = _ps - ps;
+        if (siz && keep_values) res.insert(CImg<T>(ps,1,siz,1,1,is_shared),~0U,is_shared);
+        ps = _ps;
+        while (_ps<pe && *_ps!=value) ++_ps;
+        siz = _ps - ps;
+        if (siz) res.insert(CImg<T>(ps,1,siz,1,1,is_shared),~0U,is_shared);
+        ps = _ps;
+      }
+      return res;
+    }
+
+    //! Split image into a list of one-column vectors, according to specified sequence of splitting values.
+    /**
+       \param values The splitting pattern of values.
+       \param keep_values Can be :
+          - false : Discard splitting values in resulting list.
+          - true : Keep splitting values as separate images in resulting list.
+     **/
+    template<typename t>
+    CImgList<T> get_split(const CImg<t>& values, const bool keep_values, const bool is_shared) const {
+      if (!values) return CImgList<T>(*this);
+      if (values.size()==1) return get_split(*values,keep_values,is_shared);
+      CImgList<T> res;
+      const t *pve = values.end();
+      for (const T *ps = _data, *_ps = ps, *const pe = end(); ps<pe; ) {
+
+        // Try to find match from current position.
+        const t *pv = 0;
+        do {
+          pv = values._data;
+          const T *__ps = _ps;
+          while (__ps<pe && pv<pve && *__ps==(T)*pv) { ++__ps; ++pv; }
+          if (pv==pve) _ps = __ps;
+        } while (pv==pve);
+        unsigned int siz = _ps - ps;
+        if (siz && keep_values) res.insert(CImg<T>(ps,1,siz,1,1,is_shared),~0U,is_shared); // If match found.
+        ps = _ps;
+
+        // Try to find non-match from current position.
+        do {
+          pv = values._data;
+          while (_ps<pe && *_ps!=(T)*pv) ++_ps;
+          if (_ps<pe) {
+            const T *__ps = _ps + 1;
+            ++pv;
+            while (__ps<pe && pv<pve && *__ps==(T)*pv) { ++__ps; ++pv; }
+            if (pv!=pve) _ps = __ps;
+          }
+        } while (_ps<pe && pv!=pve);
+
+        // Here, EOF of match found.
+        siz = _ps - ps;
+        if (siz) res.insert(CImg<T>(ps,1,siz,1,1,is_shared),~0U,is_shared);
+        ps = _ps;
+      }
+      return res;
+    }
+
+    //! Append an image.
+    template<typename t>
+    CImg<T>& append(const CImg<t>& img, const char axis='x', const float align=0) {
+      if (is_empty()) return assign(img,false);
+      if (!img) return *this;
+      return CImgList<T>(*this,true).insert(img).get_append(axis,align).move_to(*this);
+    }
+
+    CImg<T>& append(const CImg<T>& img, const char axis='x', const float align=0) {
+      if (is_empty()) return assign(img,false);
+      if (!img) return *this;
+      return CImgList<T>(*this,img,true).get_append(axis,align).move_to(*this);
+    }
+
+    template<typename t>
+    CImg<_cimg_Tt> get_append(const CImg<T>& img, const char axis='x', const float align=0) const {
+      if (is_empty()) return +img;
+      if (!img) return +*this;
+      return CImgList<_cimg_Tt>(*this,true).insert(img).get_append(axis,align);
+    }
+
+    CImg<T> get_append(const CImg<T>& img, const char axis='x', const float align=0) const {
+      if (is_empty()) return +img;
+      if (!img) return +*this;
+      return CImgList<T>(*this,img,true).get_append(axis,align);
+    }
+
+    //@}
+    //---------------------------------------
+    //
+    //! \name Filtering / Transforms
+    //@{
+    //---------------------------------------
+
+    //! Compute the correlation of the image instance by a mask.
+    /**
+       The correlation of the image instance \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 border_conditions = the border condition type (0=zero, 1=dirichlet)
+       \param is_normalized = enable local normalization.
+    **/
+    template<typename t>
+    CImg<T>& correlate(const CImg<t>& mask, const unsigned int border_conditions=1, const bool is_normalized=false) {
+      if (is_empty() || !mask) return *this;
+      return get_correlate(mask,border_conditions,is_normalized).move_to(*this);
+    }
+
+    template<typename t>
+    CImg<_cimg_Ttfloat> get_correlate(const CImg<t>& mask, const unsigned int border_conditions=1,
+                                      const bool is_normalized=false) const {
+      if (is_empty() || !mask) return *this;
+      typedef _cimg_Ttfloat Ttfloat;
+      CImg<Ttfloat> res(_width,_height,_depth,cimg::max(_spectrum,mask._spectrum));
+      if (border_conditions && 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 border_conditions=1)
+        Ttfloat *ptrd = res._data;
+        switch (mask._depth) {
+        case 3 : {
+          T I[27] = { 0 };
+          cimg_forC(res,c) {
+            const CImg<T> _img = get_shared_channel(c%_spectrum);
+            const CImg<t> _mask = mask.get_shared_channel(c%mask._spectrum);
+            if (is_normalized) {
+              const Ttfloat _M = (Ttfloat)_mask.magnitude(2), M = _M*_M;
+              cimg_for3x3x3(_img,x,y,z,0,I,T) {
+                const Ttfloat N = M*(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]);
+                *(ptrd++) = (Ttfloat)(N?(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])/std::sqrt(N):0);
+              }
+            } else cimg_for3x3x3(_img,x,y,z,0,I,T)
+                     *(ptrd++) = (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]);
+          }
+        } break;
+        case 2 : {
+          T I[8] = { 0 };
+          cimg_forC(res,c) {
+            const CImg<T> _img = get_shared_channel(c%_spectrum);
+            const CImg<t> _mask = mask.get_shared_channel(c%mask._spectrum);
+            if (is_normalized) {
+              const Ttfloat _M = (Ttfloat)_mask.magnitude(2), M = _M*_M;
+              cimg_for2x2x2(_img,x,y,z,0,I,T) {
+                const Ttfloat N = M*(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]);
+                *(ptrd++) = (Ttfloat)(N?(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])/std::sqrt(N):0);
+              }
+            } else cimg_for2x2x2(_img,x,y,z,0,I,T)
+                     *(ptrd++) = (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]);
+          }
+        } break;
+        default :
+        case 1 :
+          switch (mask._width) {
+          case 6 : {
+            T I[36] = { 0 };
+            cimg_forC(res,c) {
+              const CImg<T> _img = get_shared_channel(c%_spectrum);
+              const CImg<t> _mask = mask.get_shared_channel(c%mask._spectrum);
+              if (is_normalized) {
+                const Ttfloat _M = (Ttfloat)_mask.magnitude(2), M = _M*_M;
+                cimg_forZ(_img,z) cimg_for6x6(_img,x,y,z,0,I,T) {
+                  const Ttfloat N = M*(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]);
+                  *(ptrd++) = (Ttfloat)(N?(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])/std::sqrt(N):0);
+                }
+              } else cimg_forZ(_img,z) cimg_for6x6(_img,x,y,z,0,I,T)
+                       *(ptrd++) = (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]);
+            }
+          } break;
+          case 5 : {
+            T I[25] = { 0 };
+            cimg_forC(res,c) {
+              const CImg<T> _img = get_shared_channel(c%_spectrum);
+              const CImg<t> _mask = mask.get_shared_channel(c%mask._spectrum);
+              if (is_normalized) {
+                const Ttfloat _M = (Ttfloat)_mask.magnitude(2), M = _M*_M;
+                cimg_forZ(_img,z) cimg_for5x5(_img,x,y,z,0,I,T) {
+                  const Ttfloat N = M*(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]);
+                  *(ptrd++) = (Ttfloat)(N?(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])/std::sqrt(N):0);
+                }
+              } else cimg_forZ(_img,z) cimg_for5x5(_img,x,y,z,0,I,T)
+                       *(ptrd++) = (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]);
+            }
+          } break;
+          case 4 : {
+            T I[16] = { 0 };
+            cimg_forC(res,c) {
+              const CImg<T> _img = get_shared_channel(c%_spectrum);
+              const CImg<t> _mask = mask.get_shared_channel(c%mask._spectrum);
+              if (is_normalized) {
+                const Ttfloat _M = (Ttfloat)_mask.magnitude(2), M = _M*_M;
+                cimg_forZ(_img,z) cimg_for4x4(_img,x,y,z,0,I,T) {
+                  const Ttfloat N = M*(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]);
+                  *(ptrd++) = (Ttfloat)(N?(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])/std::sqrt(N):0);
+                }
+              } else cimg_forZ(_img,z) cimg_for4x4(_img,x,y,z,0,I,T)
+                       *(ptrd++) = (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]);
+            }
+          } break;
+          case 3 : {
+            T I[9] = { 0 };
+            cimg_forC(res,c) {
+              const CImg<T> _img = get_shared_channel(c%_spectrum);
+              const CImg<t> _mask = mask.get_shared_channel(c%mask._spectrum);
+              if (is_normalized) {
+                const Ttfloat _M = (Ttfloat)_mask.magnitude(2), M = _M*_M;
+                cimg_forZ(_img,z) cimg_for3x3(_img,x,y,z,0,I,T) {
+                  const Ttfloat N = M*(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]);
+                  *(ptrd++) = (Ttfloat)(N?(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])/std::sqrt(N):0);
+                }
+              } else cimg_forZ(_img,z) cimg_for3x3(_img,x,y,z,0,I,T)
+                       *(ptrd++) = (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]);
+            }
+          } break;
+          case 2 : {
+            T I[4] = { 0 };
+            cimg_forC(res,c) {
+              const CImg<T> _img = get_shared_channel(c%_spectrum);
+              const CImg<t> _mask = mask.get_shared_channel(c%mask._spectrum);
+              if (is_normalized) {
+                const Ttfloat _M = (Ttfloat)_mask.magnitude(2), M = _M*_M;
+                cimg_forZ(_img,z) cimg_for2x2(_img,x,y,z,0,I,T) {
+                  const Ttfloat N = M*(I[0]*I[0] + I[1]*I[1] +
+                                       I[2]*I[2] + I[3]*I[3]);
+                  *(ptrd++) = (Ttfloat)(N?(I[0]*_mask[0] + I[1]*_mask[1] +
+                                           I[2]*_mask[2] + I[3]*_mask[3])/std::sqrt(N):0);
+                }
+              } else cimg_forZ(_img,z) cimg_for2x2(_img,x,y,z,0,I,T)
+                       *(ptrd++) = (Ttfloat)(I[0]*_mask[0] + I[1]*_mask[1] +
+                                             I[2]*_mask[2] + I[3]*_mask[3]);
+            }
+          } break;
+          case 1 :
+            if (is_normalized) res.fill(1);
+            else cimg_forC(res,c) {
+                const CImg<T> _img = get_shared_channel(c%_spectrum);
+                const CImg<t> _mask = mask.get_shared_channel(c%mask._spectrum);
+                res.get_shared_channel(c).assign(_img)*=_mask[0];
+              }
+            break;
+          }
+        }
+      } else { // Generic version for other masks and borders conditions.
+        const int
+          mx2 = mask.width()/2, my2 = mask.height()/2, mz2 = mask.depth()/2,
+          mx1 = mx2 - 1 + (mask.width()%2), my1 = my2 - 1 + (mask.height()%2), mz1 = mz2 - 1 + (mask.depth()%2),
+          mxe = width() - mx2, mye = height() - my2, mze = depth() - mz2;
+        cimg_forC(res,c) {
+          const CImg<T> _img = get_shared_channel(c%_spectrum);
+          const CImg<t> _mask = mask.get_shared_channel(c%mask._spectrum);
+          if (is_normalized) { // Normalized correlation.
+            const Ttfloat _M = (Ttfloat)_mask.magnitude(2), M = _M*_M;
+            for (int z = mz1; z<mze; ++z)
+              for (int y = my1; y<mye; ++y)
+                for (int x = mx1; x<mxe; ++x) {
+                  Ttfloat val = 0, N = 0;
+                  for (int zm = -mz1; zm<=mz2; ++zm)
+                    for (int ym = -my1; ym<=my2; ++ym)
+                      for (int xm = -mx1; xm<=mx2; ++xm) {
+                        const Ttfloat _val = (Ttfloat)_img(x+xm,y+ym,z+zm);
+                        val+=_val*_mask(mx1+xm,my1+ym,mz1+zm);
+                        N+=_val*_val;
+                      }
+                  N*=M;
+                  res(x,y,z,c) = (Ttfloat)(N?val/std::sqrt(N):0);
+                }
+            if (border_conditions)
+              cimg_forYZ(res,y,z)
+                for (int x = 0; x<width(); (y<my1 || y>=mye || z<mz1 || z>=mze)?++x:((x<mx1-1 || x>=mxe)?++x:(x=mxe))) {
+                  Ttfloat val = 0, N = 0;
+                  for (int zm = -mz1; zm<=mz2; ++zm)
+                    for (int ym = -my1; ym<=my2; ++ym)
+                      for (int xm = -mx1; xm<=mx2; ++xm) {
+                        const Ttfloat _val = (Ttfloat)_img._atXYZ(x+xm,y+ym,z+zm);
+                        val+=_val*_mask(mx1+xm,my1+ym,mz1+zm);
+                        N+=_val*_val;
+                      }
+                  N*=M;
+                  res(x,y,z,c) = (Ttfloat)(N?val/std::sqrt(N):0);
+                }
+            else
+              cimg_forYZ(res,y,z)
+                for (int x = 0; x<width(); (y<my1 || y>=mye || z<mz1 || z>=mze)?++x:((x<mx1-1 || x>=mxe)?++x:(x=mxe))) {
+                  Ttfloat val = 0, N = 0;
+                  for (int zm = -mz1; zm<=mz2; ++zm)
+                    for (int ym = -my1; ym<=my2; ++ym)
+                      for (int xm = -mx1; xm<=mx2; ++xm) {
+                        const Ttfloat _val = (Ttfloat)_img.atXYZ(x+xm,y+ym,z+zm,0,0);
+                        val+=_val*_mask(mx1+xm,my1+ym,mz1+zm);
+                        N+=_val*_val;
+                      }
+                  N*=M;
+                  res(x,y,z,c) = (Ttfloat)(N?val/std::sqrt(N):0);
+                }
+          } else { // 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+=_img(x+xm,y+ym,z+zm)*_mask(mx1+xm,my1+ym,mz1+zm);
+                  res(x,y,z,c) = (Ttfloat)val;
+                }
+            if (border_conditions)
+              cimg_forYZ(res,y,z)
+                for (int x = 0; x<width(); (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+=_img._atXYZ(x+xm,y+ym,z+zm)*_mask(mx1+xm,my1+ym,mz1+zm);
+                  res(x,y,z,c) = (Ttfloat)val;
+                }
+            else
+              cimg_forYZ(res,y,z)
+                for (int x = 0; x<width(); (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+=_img.atXYZ(x+xm,y+ym,z+zm,0,0)*_mask(mx1+xm,my1+ym,mz1+zm);
+                  res(x,y,z,c) = (Ttfloat)val;
+                }
+          }
+        }
+      }
+      return res;
+    }
+
+    //! 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 border_conditions = the border condition type (0=zero, 1=dirichlet)
+       \param is_normalized = enable local normalization.
+    **/
+    template<typename t>
+    CImg<T>& convolve(const CImg<t>& mask, const unsigned int border_conditions=1, const bool is_normalized=false) {
+      if (is_empty() || !mask) return *this;
+      return get_convolve(mask,border_conditions,is_normalized).move_to(*this);
+    }
+
+    template<typename t>
+    CImg<_cimg_Ttfloat> get_convolve(const CImg<t>& mask, const unsigned int border_conditions=1,
+                                     const bool is_normalized=false) const {
+      if (is_empty() || !mask) return *this;
+      return get_correlate(CImg<t>(mask._data,mask.size(),1,1,1,true).get_mirror('x').resize(mask,-1),border_conditions,is_normalized);
+    }
+
+    //! Return the erosion of the image by a structuring element.
+    template<typename t>
+    CImg<T>& erode(const CImg<t>& mask, const unsigned int border_conditions=1, const bool is_normalized=false) {
+      if (is_empty() || !mask) return *this;
+      return get_erode(mask,border_conditions,is_normalized).move_to(*this);
+    }
+
+    template<typename t>
+    CImg<_cimg_Tt> get_erode(const CImg<t>& mask, const unsigned int border_conditions=1,
+                             const bool is_normalized=false) const {
+      if (is_empty() || !mask) return *this;
+      typedef _cimg_Tt Tt;
+      CImg<Tt> res(_width,_height,_depth,cimg::max(_spectrum,mask._spectrum));
+      const int
+        mx2 = mask.width()/2, my2 = mask.height()/2, mz2 = mask.depth()/2,
+        mx1 = mx2 - 1 + (mask.width()%2), my1 = my2 - 1 + (mask.height()%2), mz1 = mz2 - 1 + (mask.depth()%2),
+        mxe = width() - mx2, mye = height() - my2, mze = depth() - mz2;
+      cimg_forC(*this,c) {
+        const CImg<T> _img = get_shared_channel(c%_spectrum);
+        const CImg<t> _mask = mask.get_shared_channel(c%mask._spectrum);
+        if (is_normalized) { // Normalized 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)(_img(x+xm,y+ym,z+zm) + mval);
+                      if (mval && cval<min_val) min_val = cval;
+                    }
+                res(x,y,z,c) = min_val;
+              }
+          if (border_conditions)
+            cimg_forYZ(res,y,z)
+              for (int x = 0; x<width(); (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)(_img._atXYZ(x+xm,y+ym,z+zm) + mval);
+                      if (mval && cval<min_val) min_val = cval;
+                    }
+                res(x,y,z,c) = min_val;
+              }
+          else
+            cimg_forYZ(res,y,z)
+              for (int x = 0; x<width(); (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)(_img.atXYZ(x+xm,y+ym,z+zm,0,0) + mval);
+                      if (mval && cval<min_val) min_val = cval;
+                    }
+                res(x,y,z,c) = min_val;
+              }
+        } else { // 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)_img(x+xm,y+ym,z+zm);
+                      if (_mask(mx1+xm,my1+ym,mz1+zm) && cval<min_val) min_val = cval;
+                    }
+                res(x,y,z,c) = min_val;
+              }
+          if (border_conditions)
+            cimg_forYZ(res,y,z)
+              for (int x = 0; x<width(); (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)_img._atXYZ(x+xm,y+ym,z+zm);
+                      if (_mask(mx1+xm,my1+ym,mz1+zm) && cval<min_val) min_val = cval;
+                    }
+                res(x,y,z,c) = min_val;
+              }
+          else
+            cimg_forYZ(res,y,z)
+              for (int x = 0; x<width(); (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)_img.atXYZ(x+xm,y+ym,z+zm,0,0);
+                      if (_mask(mx1+xm,my1+ym,mz1+zm) && cval<min_val) min_val = cval;
+                    }
+                res(x,y,z,c) = min_val;
+              }
+        }
+      }
+      return res;
+    }
+
+    //! Erode the image by a rectangular structuring element of size sx,sy,sz.
+    CImg<T>& erode(const unsigned int sx, const unsigned int sy, const unsigned int sz=1) {
+      if (is_empty() || (sx==1 && sy==1 && sz==1)) return *this;
+      if (sx>1 && _width>1) { // Along X-axis.
+        const int L = width(), off = 1, s = (int)sx, _s1 = s/2, _s2 = s - _s1, s1 = _s1>L?L:_s1, s2 = _s2>L?L:_s2;
+        CImg<T> buf(L);
+        T *const ptrdb = buf._data, *ptrd = ptrdb, *const ptrde = buf._data + L - 1;
+        cimg_forYZC(*this,y,z,c) {
+          const T *const ptrsb = data(0,y,z,c), *ptrs = ptrsb, *const ptrse = ptrs + L*off - off;
+          ptrd = buf._data; T cur = *ptrs; ptrs+=off; bool is_first = true;
+          for (int p = s2-1; p>0 && ptrs<=ptrse; --p) { const T val = *ptrs; ptrs+=off; if (val<=cur) { cur = val; is_first = false; }} *(ptrd++) = cur;
+          for (int p = s1; p>0 && ptrd<=ptrde; --p) { const T val = *ptrs; if (ptrs<ptrse) ptrs+=off; if (val<=cur) { cur = val; is_first = false; } *(ptrd++) = cur; }
+          for (int p = L - s - 1; p>0; --p) {
+            const T val = *ptrs; ptrs+=off;
+            if (is_first) {
+              const T *nptrs = ptrs - off; cur = val;
+              for (int q = s - 2; q>0; --q) { nptrs-=off; const T nval = *nptrs; if (nval<cur) cur = nval; }
+              nptrs-=off; const T nval = *nptrs; if (nval<cur) { cur = nval; is_first = true; } else is_first = false;
+            } else { if (val<=cur) cur = val; else if (cur==*(ptrs-s*off)) is_first = true; }
+            *(ptrd++) = cur;
+          }
+          ptrd = ptrde; ptrs = ptrse; cur = *ptrs; ptrs-=off;
+          for (int p = s1; p>0 && ptrs>=ptrsb; --p) { const T val = *ptrs; ptrs-=off; if (val<cur) cur = val; } *(ptrd--) = cur;
+          for (int p = s2-1; p>0 && ptrd>=ptrdb; --p) { const T val = *ptrs; if (ptrs>ptrsb) ptrs-=off; if (val<cur) cur = val; *(ptrd--) = cur; }
+          T *pd = data(_width-1,y,z,c) + off; cimg_for(buf,ps,T) { pd-=off; *pd = *ps; }
+        }
+      }
+
+      if (sy>1 && _height>1) { // Along Y-axis.
+        const int L = height(), off = width(), s = (int)sy, _s1 = s/2, _s2 = s - _s1, s1 = _s1>L?L:_s1, s2 = _s2>L?L:_s2;
+        CImg<T> buf(L);
+        T *const ptrdb = buf._data, *ptrd = ptrdb, *const ptrde = buf._data + L - 1;
+        cimg_forXZC(*this,x,z,c) {
+          const T *const ptrsb = data(x,0,z,c), *ptrs = ptrsb, *const ptrse = ptrs + L*off - off;
+          ptrd = buf._data; T cur = *ptrs; ptrs+=off; bool is_first = true;
+          for (int p = s2-1; p>0 && ptrs<=ptrse; --p) { const T val = *ptrs; ptrs+=off; if (val<=cur) { cur = val; is_first = false; }} *(ptrd++) = cur;
+          for (int p = s1; p>0 && ptrd<=ptrde; --p) { const T val = *ptrs; if (ptrs<ptrse) ptrs+=off; if (val<=cur) { cur = val; is_first = false; } *(ptrd++) = cur; }
+          for (int p = L - s - 1; p>0; --p) {
+            const T val = *ptrs; ptrs+=off;
+            if (is_first) {
+              const T *nptrs = ptrs - off; cur = val;
+              for (int q = s - 2; q>0; --q) { nptrs-=off; const T nval = *nptrs; if (nval<cur) cur = nval; }
+              nptrs-=off; const T nval = *nptrs; if (nval<cur) { cur = nval; is_first = true; } else is_first = false;
+            } else { if (val<=cur) cur = val; else if (cur==*(ptrs-s*off)) is_first = true; }
+            *(ptrd++) = cur;
+          }
+          ptrd = ptrde; ptrs = ptrse; cur = *ptrs; ptrs-=off;
+          for (int p = s1; p>0 && ptrs>=ptrsb; --p) { const T val = *ptrs; ptrs-=off; if (val<cur) cur = val; } *(ptrd--) = cur;
+          for (int p = s2-1; p>0 && ptrd>=ptrdb; --p) { const T val = *ptrs; if (ptrs>ptrsb) ptrs-=off; if (val<cur) cur = val; *(ptrd--) = cur; }
+          T *pd = data(x,_height-1,z,c) + off; cimg_for(buf,ps,T) { pd-=off; *pd = *ps; }
+        }
+      }
+
+      if (sz>1 && _depth>1) { // Along Z-axis.
+        const int L = depth(), off = width()*height(), s = (int)sz, _s1 = s/2, _s2 = s - _s1, s1 = _s1>L?L:_s1, s2 = _s2>L?L:_s2;
+        CImg<T> buf(L);
+        T *const ptrdb = buf._data, *ptrd = ptrdb, *const ptrde = buf._data + L - 1;
+        cimg_forXYC(*this,x,y,c) {
+          const T *const ptrsb = data(x,y,0,c), *ptrs = ptrsb, *const ptrse = ptrs + L*off - off;
+          ptrd = buf._data; T cur = *ptrs; ptrs+=off; bool is_first = true;
+          for (int p = s2-1; p>0 && ptrs<=ptrse; --p) { const T val = *ptrs; ptrs+=off; if (val<=cur) { cur = val; is_first = false; }} *(ptrd++) = cur;
+          for (int p = s1; p>0 && ptrd<=ptrde; --p) { const T val = *ptrs; if (ptrs<ptrse) ptrs+=off; if (val<=cur) { cur = val; is_first = false; } *(ptrd++) = cur; }
+          for (int p = L - s - 1; p>0; --p) {
+            const T val = *ptrs; ptrs+=off;
+            if (is_first) {
+              const T *nptrs = ptrs - off; cur = val;
+              for (int q = s - 2; q>0; --q) { nptrs-=off; const T nval = *nptrs; if (nval<cur) cur = nval; }
+              nptrs-=off; const T nval = *nptrs; if (nval<cur) { cur = nval; is_first = true; } else is_first = false;
+            } else { if (val<=cur) cur = val; else if (cur==*(ptrs-s*off)) is_first = true; }
+            *(ptrd++) = cur;
+          }
+          ptrd = ptrde; ptrs = ptrse; cur = *ptrs; ptrs-=off;
+          for (int p = s1; p>0 && ptrs>=ptrsb; --p) { const T val = *ptrs; ptrs-=off; if (val<cur) cur = val; } *(ptrd--) = cur;
+          for (int p = s2-1; p>0 && ptrd>=ptrdb; --p) { const T val = *ptrs; if (ptrs>ptrsb) ptrs-=off; if (val<cur) cur = val; *(ptrd--) = cur; }
+          T *pd = data(x,y,_depth-1,c) + off; cimg_for(buf,ps,T) { pd-=off; *pd = *ps; }
+        }
+      }
+      return *this;
+    }
+
+    CImg<T> get_erode(const unsigned int sx, const unsigned int sy, const unsigned int sz=1) const {
+      return (+*this).erode(sx,sy,sz);
+    }
+
+    //! Erode the image by a square structuring element of size sx.
+    CImg<T>& erode(const unsigned int s) {
+      return erode(s,s,s);
+    }
+
+    CImg<T> get_erode(const unsigned int s) const {
+      return (+*this).erode(s);
+    }
+
+    //! Dilate the image by a structuring element.
+    template<typename t>
+    CImg<T>& dilate(const CImg<t>& mask, const unsigned int border_conditions=1, const bool is_normalized=false) {
+      if (is_empty() || !mask) return *this;
+      return get_dilate(mask,border_conditions,is_normalized).move_to(*this);
+    }
+
+    template<typename t>
+    CImg<_cimg_Tt> get_dilate(const CImg<t>& mask, const unsigned int border_conditions=1,
+                              const bool is_normalized=false) const {
+      if (is_empty() || !mask) return *this;
+      typedef _cimg_Tt Tt;
+      CImg<Tt> res(_width,_height,_depth,_spectrum);
+      const int
+        mx2 = mask.width()/2, my2 = mask.height()/2, mz2 = mask.depth()/2,
+        mx1 = mx2 - 1 + (mask.width()%2), my1 = my2 - 1 + (mask.height()%2), mz1 = mz2 - 1 + (mask.depth()%2),
+        mxe = width() - mx2, mye = height() - my2, mze = depth() - mz2;
+      cimg_forC(*this,c) {
+        const CImg<T> _img = get_shared_channel(c%_spectrum);
+        const CImg<t> _mask = mask.get_shared_channel(c%mask._spectrum);
+        if (is_normalized) { // Normalized dilation.
+          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)(_img(x+xm,y+ym,z+zm) - mval);
+                      if (mval && cval>max_val) max_val = cval;
+                    }
+                res(x,y,z,c) = max_val;
+              }
+          if (border_conditions)
+            cimg_forYZ(res,y,z)
+              for (int x = 0; x<width(); (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)(_img._atXYZ(x+xm,y+ym,z+zm) - mval);
+                      if (mval && cval>max_val) max_val = cval;
+                    }
+                res(x,y,z,c) = max_val;
+              }
+          else
+            cimg_forYZ(*this,y,z)
+              for (int x = 0; x<width(); (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)(_img.atXYZ(x+xm,y+ym,z+zm,0,0) - mval);
+                      if (mval && cval>max_val) max_val = cval;
+                    }
+                res(x,y,z,c) = max_val;
+              }
+        } else { // Classical dilation.
+          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)_img(x+xm,y+ym,z+zm);
+                      if (_mask(mx1+xm,my1+ym,mz1+zm) && cval>max_val) max_val = cval;
+                    }
+                res(x,y,z,c) = max_val;
+              }
+          if (border_conditions)
+            cimg_forYZ(res,y,z)
+              for (int x = 0; x<width(); (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)_img._atXYZ(x+xm,y+ym,z+zm);
+                      if (_mask(mx1+xm,my1+ym,mz1+zm) && cval>max_val) max_val = cval;
+                    }
+                res(x,y,z,c) = max_val;
+              }
+          else
+            cimg_forYZ(res,y,z)
+              for (int x = 0; x<width(); (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)_img.atXYZ(x+xm,y+ym,z+zm,0,0);
+                      if (_mask(mx1+xm,my1+ym,mz1+zm) && cval>max_val) max_val = cval;
+                    }
+                res(x,y,z,c) = max_val;
+              }
+        }
+      }
+      return res;
+    }
+
+    //! Dilate the image by a rectangular structuring element of size sx,sy,sz.
+    CImg<T>& dilate(const unsigned int sx, const unsigned int sy, const unsigned int sz=1) {
+      if (is_empty() || (sx==1 && sy==1 && sz==1)) return *this;
+      if (sx>1 && _width>1) { // Along X-axis.
+        const int L = width(), off = 1, s = (int)sx, _s2 = s/2 + 1, _s1 = s - _s2, s1 = _s1>L?L:_s1, s2 = _s2>L?L:_s2;
+        CImg<T> buf(L);
+        T *const ptrdb = buf._data, *ptrd = ptrdb, *const ptrde = buf._data + L - 1;
+        cimg_forYZC(*this,y,z,c) {
+          const T *const ptrsb = data(0,y,z,c), *ptrs = ptrsb, *const ptrse = ptrs + L*off - off;
+          ptrd = buf._data; T cur = *ptrs; ptrs+=off; bool is_first = true;
+          for (int p = s2-1; p>0 && ptrs<=ptrse; --p) { const T val = *ptrs; ptrs+=off; if (val>=cur) { cur = val; is_first = false; }} *(ptrd++) = cur;
+          for (int p = s1; p>0 && ptrd<=ptrde; --p) { const T val = *ptrs; if (ptrs<ptrse) ptrs+=off; if (val>=cur) { cur = val; is_first = false; } *(ptrd++) = cur; }
+          for (int p = L - s - 1; p>0; --p) {
+            const T val = *ptrs; ptrs+=off;
+            if (is_first) {
+              const T *nptrs = ptrs - off; cur = val;
+              for (int q = s - 2; q>0; --q) { nptrs-=off; const T nval = *nptrs; if (nval>cur) cur = nval; }
+              nptrs-=off; const T nval = *nptrs; if (nval>cur) { cur = nval; is_first = true; } else is_first = false;
+            } else { if (val>=cur) cur = val; else if (cur==*(ptrs-s*off)) is_first = true; }
+            *(ptrd++) = cur;
+          }
+          ptrd = ptrde; ptrs = ptrse; cur = *ptrs; ptrs-=off;
+          for (int p = s1; p>0 && ptrs>=ptrsb; --p) { const T val = *ptrs; ptrs-=off; if (val>cur) cur = val; } *(ptrd--) = cur;
+          for (int p = s2-1; p>0 && ptrd>=ptrdb; --p) { const T val = *ptrs; if (ptrs>ptrsb) ptrs-=off; if (val>cur) cur = val; *(ptrd--) = cur; }
+          T *pd = data(_width-1,y,z,c) + off; cimg_for(buf,ps,T) { pd-=off; *pd = *ps; }
+        }
+      }
+
+      if (sy>1 && _height>1) { // Along Y-axis.
+        const int L = height(), off = width(), s = (int)sy, _s2 = s/2 + 1, _s1 = s - _s2, s1 = _s1>L?L:_s1, s2 = _s2>L?L:_s2;
+        CImg<T> buf(L);
+        T *const ptrdb = buf._data, *ptrd = ptrdb, *const ptrde = buf._data + L - 1;
+        cimg_forXZC(*this,x,z,c) {
+          const T *const ptrsb = data(x,0,z,c), *ptrs = ptrsb, *const ptrse = ptrs + L*off - off;
+          ptrd = buf._data; T cur = *ptrs; ptrs+=off; bool is_first = true;
+          for (int p = s2-1; p>0 && ptrs<=ptrse; --p) { const T val = *ptrs; ptrs+=off; if (val>=cur) { cur = val; is_first = false; }} *(ptrd++) = cur;
+          for (int p = s1; p>0 && ptrd<=ptrde; --p) { const T val = *ptrs; if (ptrs<ptrse) ptrs+=off; if (val>=cur) { cur = val; is_first = false; } *(ptrd++) = cur; }
+          for (int p = L - s - 1; p>0; --p) {
+            const T val = *ptrs; ptrs+=off;
+            if (is_first) {
+              const T *nptrs = ptrs - off; cur = val;
+              for (int q = s - 2; q>0; --q) { nptrs-=off; const T nval = *nptrs; if (nval>cur) cur = nval; }
+              nptrs-=off; const T nval = *nptrs; if (nval>cur) { cur = nval; is_first = true; } else is_first = false;
+            } else { if (val>=cur) cur = val; else if (cur==*(ptrs-s*off)) is_first = true; }
+            *(ptrd++) = cur;
+          }
+          ptrd = ptrde; ptrs = ptrse; cur = *ptrs; ptrs-=off;
+          for (int p = s1; p>0 && ptrs>=ptrsb; --p) { const T val = *ptrs; ptrs-=off; if (val>cur) cur = val; } *(ptrd--) = cur;
+          for (int p = s2-1; p>0 && ptrd>=ptrdb; --p) { const T val = *ptrs; if (ptrs>ptrsb) ptrs-=off; if (val>cur) cur = val; *(ptrd--) = cur; }
+          T *pd = data(x,_height-1,z,c) + off; cimg_for(buf,ps,T) { pd-=off; *pd = *ps; }
+        }
+      }
+
+      if (sz>1 && _depth>1) { // Along Z-axis.
+        const int L = depth(), off = width()*height(), s = (int)sz, _s2 = s/2 + 1, _s1 = s - _s2, s1 = _s1>L?L:_s1, s2 = _s2>L?L:_s2;
+        CImg<T> buf(L);
+        T *const ptrdb = buf._data, *ptrd = ptrdb, *const ptrde = buf._data + L - 1;
+        cimg_forXYC(*this,x,y,c) {
+          const T *const ptrsb = data(x,y,0,c), *ptrs = ptrsb, *const ptrse = ptrs + L*off - off;
+          ptrd = buf._data; T cur = *ptrs; ptrs+=off; bool is_first = true;
+          for (int p = s2-1; p>0 && ptrs<=ptrse; --p) { const T val = *ptrs; ptrs+=off; if (val>=cur) { cur = val; is_first = false; }} *(ptrd++) = cur;
+          for (int p = s1; p>0 && ptrd<=ptrde; --p) { const T val = *ptrs; if (ptrs<ptrse) ptrs+=off; if (val>=cur) { cur = val; is_first = false; } *(ptrd++) = cur; }
+          for (int p = L - s - 1; p>0; --p) {
+            const T val = *ptrs; ptrs+=off;
+            if (is_first) {
+              const T *nptrs = ptrs - off; cur = val;
+              for (int q = s - 2; q>0; --q) { nptrs-=off; const T nval = *nptrs; if (nval>cur) cur = nval; }
+              nptrs-=off; const T nval = *nptrs; if (nval>cur) { cur = nval; is_first = true; } else is_first = false;
+            } else { if (val>=cur) cur = val; else if (cur==*(ptrs-s*off)) is_first = true; }
+            *(ptrd++) = cur;
+          }
+          ptrd = ptrde; ptrs = ptrse; cur = *ptrs; ptrs-=off;
+          for (int p = s1; p>0 && ptrs>=ptrsb; --p) { const T val = *ptrs; ptrs-=off; if (val>cur) cur = val; } *(ptrd--) = cur;
+          for (int p = s2-1; p>0 && ptrd>=ptrdb; --p) { const T val = *ptrs; if (ptrs>ptrsb) ptrs-=off; if (val>cur) cur = val; *(ptrd--) = cur; }
+          T *pd = data(x,y,_depth-1,c) + off; cimg_for(buf,ps,T) { pd-=off; *pd = *ps; }
+        }
+      }
+      return *this;
+    }
+
+    CImg<T> get_dilate(const unsigned int sx, const unsigned int sy, const unsigned int sz=1) const {
+      return (+*this).dilate(sx,sy,sz);
+    }
+
+    //! Erode the image by a square structuring element of size sx.
+    CImg<T>& dilate(const unsigned int s) {
+      return dilate(s,s,s);
+    }
+
+    CImg<T> get_dilate(const unsigned int s) const {
+      return (+*this).dilate(s);
+    }
+
+    //! Compute the watershed transform, from an image instance of non-zero labels.
+    template<typename t>
+    CImg<T>& watershed(const CImg<t>& priority, const bool fill_lines=true) {
+      if (is_empty()) return *this;
+      if (!is_sameXYZ(priority))
+        throw CImgArgumentException(_cimg_instance
+                                    "watershed() : image instance and specified priority (%u,%u,%u,%u,%p) have different dimensions.",
+                                    cimg_instance,priority._width,priority._height,priority._depth,priority._spectrum,priority._data);
+      if (_spectrum!=1) { cimg_forC(*this,c) get_shared_channel(c).watershed(priority.get_shared_channel(c%priority._spectrum),fill_lines); return *this; }
+
+      CImg<boolT> in_queue(_width,_height,_depth,1,0);
+      CImg<typename cimg::superset2<T,t,int>::type> Q;
+      unsigned int sizeQ = 0;
+
+      // Find seed points and insert them in priority queue.
+      const T *ptrs = _data;
+      cimg_forXYZ(*this,x,y,z) if (*(ptrs++)) {
+        if (x-1>=0 && !(*this)(x-1,y,z))       Q._priority_queue_insert(in_queue,sizeQ,priority(x-1,y,z),x-1,y,z);
+        if (x+1<width() && !(*this)(x+1,y,z))  Q._priority_queue_insert(in_queue,sizeQ,priority(x+1,y,z),x+1,y,z);
+        if (y-1>=0 && !(*this)(x,y-1,z))       Q._priority_queue_insert(in_queue,sizeQ,priority(x,y-1,z),x,y-1,z);
+        if (y+1<height() && !(*this)(x,y+1,z)) Q._priority_queue_insert(in_queue,sizeQ,priority(x,y+1,z),x,y+1,z);
+        if (z-1>=0 && !(*this)(x,y,z-1))       Q._priority_queue_insert(in_queue,sizeQ,priority(x,y,z-1),x,y,z-1);
+        if (z+1<depth() && !(*this)(x,y,z+1))  Q._priority_queue_insert(in_queue,sizeQ,priority(x,y,z+1),x,y,z+1);
+      }
+
+      // Start watershed computation.
+      while (sizeQ) {
+
+        // Get and remove point with maximal priority from the queue.
+        const int x = (int)Q(0,1), y = (int)Q(0,2), z = (int)Q(0,3);
+        Q._priority_queue_remove(sizeQ);
+
+        // Check labels of the neighbors.
+        bool is_same_label = true;
+        unsigned int label = 0;
+        if (x-1>=0) {
+          if ((*this)(x-1,y,z)) { if (!label) label = (unsigned int)(*this)(x-1,y,z); else if (label!=(*this)(x-1,y,z)) is_same_label = false; }
+          else Q._priority_queue_insert(in_queue,sizeQ,priority(x-1,y,z),x-1,y,z);
+        }
+        if (x+1<width()) {
+          if ((*this)(x+1,y,z)) { if (!label) label = (unsigned int)(*this)(x+1,y,z); else if (label!=(*this)(x+1,y,z)) is_same_label = false; }
+          else Q._priority_queue_insert(in_queue,sizeQ,priority(x+1,y,z),x+1,y,z);
+        }
+        if (y-1>=0) {
+          if ((*this)(x,y-1,z)) { if (!label) label = (unsigned int)(*this)(x,y-1,z); else if (label!=(*this)(x,y-1,z)) is_same_label = false; }
+          else Q._priority_queue_insert(in_queue,sizeQ,priority(x,y-1,z),x,y-1,z);
+        }
+        if (y+1<height()) {
+          if ((*this)(x,y+1,z)) { if (!label) label = (unsigned int)(*this)(x,y+1,z); else if (label!=(*this)(x,y+1,z)) is_same_label = false; }
+          else Q._priority_queue_insert(in_queue,sizeQ,priority(x,y+1,z),x,y+1,z);
+        }
+        if (z-1>=0) {
+          if ((*this)(x,y,z-1)) { if (!label) label = (unsigned int)(*this)(x,y,z-1); else if (label!=(*this)(x,y,z-1)) is_same_label = false; }
+          else Q._priority_queue_insert(in_queue,sizeQ,priority(x,y,z-1),x,y,z-1);
+        }
+        if (z+1<depth()) {
+          if ((*this)(x,y,z+1)) { if (!label) label = (unsigned int)(*this)(x,y,z+1); else if (label!=(*this)(x,y,z+1)) is_same_label = false; }
+          else Q._priority_queue_insert(in_queue,sizeQ,priority(x,y,z+1),x,y,z+1);
+        }
+        if (is_same_label) (*this)(x,y,z) = label;
+      }
+
+      // Fill lines.
+      if (fill_lines) {
+
+        // Sort all non-labeled pixels with labeled neighbors.
+        in_queue = false;
+        const T *ptrs = _data;
+        cimg_forXYZ(*this,x,y,z) if (!*(ptrs++) &&
+                                     ((x-1>=0 && (*this)(x-1,y,z)) || (x+1<width() && (*this)(x+1,y,z)) ||
+                                      (y-1>=0 && (*this)(x,y-1,z)) || (y+1<height() && (*this)(x,y+1,z)) ||
+                                      (z-1>=0 && (*this)(x,y,z-1)) || (z+1>depth() && (*this)(x,y,z+1))))
+          Q._priority_queue_insert(in_queue,sizeQ,priority(x,y,z),x,y,z);
+
+        // Start line filling process.
+        while (sizeQ) {
+          const int x = (int)Q(0,1), y = (int)Q(0,2), z = (int)Q(0,3);
+          Q._priority_queue_remove(sizeQ);
+          t pmax = cimg::type<t>::min();
+          int xmax = 0, ymax = 0, zmax = 0;
+          if (x-1>=0) {
+            if ((*this)(x-1,y,z)) { if (priority(x-1,y,z)>pmax) { pmax = priority(x-1,y,z); xmax = x-1; ymax = y; zmax = z; }}
+            else Q._priority_queue_insert(in_queue,sizeQ,priority(x-1,y,z),x-1,y,z);
+          }
+          if (x+1<width()) {
+            if ((*this)(x+1,y,z)) { if (priority(x+1,y,z)>pmax) { pmax = priority(x+1,y,z); xmax = x+1; ymax = y; zmax = z; }}
+            else Q._priority_queue_insert(in_queue,sizeQ,priority(x+1,y,z),x+1,y,z);
+          }
+          if (y-1>=0) {
+            if ((*this)(x,y-1,z)) { if (priority(x,y-1,z)>pmax) { pmax = priority(x,y-1,z); xmax = x; ymax = y-1; zmax = z; }}
+            else Q._priority_queue_insert(in_queue,sizeQ,priority(x,y-1,z),x,y-1,z);
+          }
+          if (y+1<height()) {
+            if ((*this)(x,y+1,z)) { if (priority(x,y+1,z)>pmax) { pmax = priority(x,y+1,z); xmax = x; ymax = y+1; zmax = z; }}
+            else Q._priority_queue_insert(in_queue,sizeQ,priority(x,y+1,z),x,y+1,z);
+          }
+          if (z-1>=0) {
+            if ((*this)(x,y,z-1)) { if (priority(x,y,z-1)>pmax) { pmax = priority(x,y,z-1); xmax = x; ymax = y; zmax = z-1; }}
+            else Q._priority_queue_insert(in_queue,sizeQ,priority(x,y,z-1),x,y,z-1);
+          }
+          if (z+1<depth()) {
+            if ((*this)(x,y,z+1)) { if (priority(x,y,z+1)>pmax) { pmax = priority(x,y,z+1); xmax = x; ymax = y; zmax = z+1; }}
+            else Q._priority_queue_insert(in_queue,sizeQ,priority(x,y,z+1),x,y,z+1);
+          }
+          (*this)(x,y,z) = (*this)(xmax,ymax,zmax);
+        }
+      }
+      return *this;
+    }
+
+    template<typename t>
+    CImg<T> get_watershed(const CImg<t>& priority, const bool fill_lines=true) const {
+      return (+*this).watershed(priority,fill_lines);
+    }
+
+    // Insert/Remove items in priority queue, for watershed/distance transforms.
+    template<typename t>
+    bool _priority_queue_insert(CImg<boolT>& in_queue, unsigned int& siz, const t value, const unsigned int x, const unsigned int y, const unsigned int z) {
+      if (in_queue(x,y,z)) return false;
+      in_queue(x,y,z) = true;
+      if (++siz>=_width) { if (!is_empty()) resize(_width*2,4,1,1,0); else assign(64,4); }
+      (*this)(siz-1,0) = (T)value; (*this)(siz-1,1) = (T)x; (*this)(siz-1,2) = (T)y; (*this)(siz-1,3) = (T)z;
+      for (unsigned int pos = siz - 1, par = 0; pos && value>(*this)(par=(pos+1)/2-1,0); pos = par) {
+        cimg::swap((*this)(pos,0),(*this)(par,0)); cimg::swap((*this)(pos,1),(*this)(par,1));
+        cimg::swap((*this)(pos,2),(*this)(par,2)); cimg::swap((*this)(pos,3),(*this)(par,3));
+      }
+      return true;
+    }
+
+    CImg<T>& _priority_queue_remove(unsigned int& siz) {
+      (*this)(0,0) = (*this)(--siz,0); (*this)(0,1) = (*this)(siz,1); (*this)(0,2) = (*this)(siz,2); (*this)(0,3) = (*this)(siz,3);
+      const float value = (*this)(0,0);
+      for (unsigned int pos = 0, left = 0, right = 0;
+           ((right=2*(pos+1),(left=right-1))<siz && value<(*this)(left,0)) || (right<siz && value<(*this)(right,0));) {
+        if (right<siz) {
+          if ((*this)(left,0)>(*this)(right,0)) {
+            cimg::swap((*this)(pos,0),(*this)(left,0)); cimg::swap((*this)(pos,1),(*this)(left,1));
+            cimg::swap((*this)(pos,2),(*this)(left,2)); cimg::swap((*this)(pos,3),(*this)(left,3));
+            pos = left;
+          } else {
+            cimg::swap((*this)(pos,0),(*this)(right,0)); cimg::swap((*this)(pos,1),(*this)(right,1));
+            cimg::swap((*this)(pos,2),(*this)(right,2)); cimg::swap((*this)(pos,3),(*this)(right,3));
+            pos = right;
+          }
+        } else {
+          cimg::swap((*this)(pos,0),(*this)(left,0)); cimg::swap((*this)(pos,1),(*this)(left,1));
+          cimg::swap((*this)(pos,2),(*this)(left,2)); cimg::swap((*this)(pos,3),(*this)(left,3));
+          pos = left;
+        }
+      }
+      return *this;
+    }
+
+    //! 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); \
+  }
+      const char naxis = cimg::uncase(axis);
+      const float nsigma = sigma>=0?sigma:-sigma*(naxis=='x'?_width:naxis=='y'?_height:naxis=='z'?_depth:_spectrum)/100;
+      if (is_empty() || (nsigma<0.1 && !order)) return *this;
+      const float
+        nnsigma = nsigma<0.1f?0.1f:nsigma,
+        alpha = 1.695f/nnsigma,
+        ema = (float)std::exp(-alpha),
+        ema2 = (float)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)*(1-ema)/(2*(ema+1)*ema);
+        a0 = a3 = 0;
+        a1 = k*ema;
+        a2 = -a1;
+      } break;
+      case 2 : {
+        const float
+          ea = (float)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_instance
+                                    "deriche() : Invalid specified filter order %u "
+                                    "(should be { 0=smoothing | 1=1st-derivative | 2=2nd-derivative }).",
+                                    cimg_instance,
+                                    order);
+      }
+      coefp = (a0+a1)/(1+b1+b2);
+      coefn = (a2+a3)/(1+b1+b2);
+      switch (naxis) {
+      case 'x' : {
+        const int N = _width, off = 1;
+        CImg<Tfloat> Y(N);
+        cimg_forYZC(*this,y,z,c) { T *ptrX = data(0,y,z,c); _cimg_deriche2_apply; }
+      } break;
+      case 'y' : {
+        const int N = _height, off = _width;
+        CImg<Tfloat> Y(N);
+        cimg_forXZC(*this,x,z,c) { T *ptrX = data(x,0,z,c); _cimg_deriche2_apply; }
+      } break;
+      case 'z' : {
+        const int N = _depth, off = _width*_height;
+        CImg<Tfloat> Y(N);
+        cimg_forXYC(*this,x,y,c) { T *ptrX = data(x,y,0,c); _cimg_deriche2_apply; }
+      } break;
+      default : {
+        const int N = _spectrum, off = _width*_height*_depth;
+        CImg<Tfloat> Y(N);
+        cimg_forXYZ(*this,x,y,z) { T *ptrX = data(x,y,z,0); _cimg_deriche2_apply; }
+      }
+      }
+      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) deriche(sigmax,0,'x',cond);
+        if (_height>1) deriche(sigmay,0,'y',cond);
+        if (_depth>1) 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) {
+      const float nsigma = sigma>=0?sigma:-sigma*cimg::max(_width,_height,_depth)/100;
+      return blur(nsigma,nsigma,nsigma,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/vectors 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=1) {
+
+      // Check arguments and init variables
+      if (!is_sameXYZ(G) || (G._spectrum!=3 && G._spectrum!=6))
+        throw CImgArgumentException(_cimg_instance
+                                    "blur_anisotropic() : Invalid specified diffusion tensor field (%u,%u,%u,%u,%p).",
+                                    cimg_instance,
+                                    G._width,G._height,G._depth,G._spectrum,G._data);
+
+      if (is_empty() || amplitude<=0 || dl<0) return *this;
+      const bool is_3d = (G._spectrum==6);
+      T val_min, val_max = max_min(val_min);
+
+      if (da<=0) {  // Iterated oriented Laplacians
+        CImg<Tfloat> velocity(_width,_height,_depth,_spectrum);
+        for (unsigned int iteration = 0; iteration<(unsigned int)amplitude; ++iteration) {
+          Tfloat *ptrd = velocity._data, veloc_max = 0;
+          if (is_3d) { // 3d version
+            CImg_3x3x3(I,Tfloat);
+            cimg_forC(*this,c) cimg_for3x3x3(*this,x,y,z,c,I,Tfloat) {
+              const Tfloat
+                ixx = Incc + Ipcc - 2*Iccc,
+                ixy = (Innc + Ippc - Inpc - Ipnc)/4,
+                ixz = (Incn + Ipcp - Incp - Ipcn)/4,
+                iyy = Icnc + Icpc - 2*Iccc,
+                iyz = (Icnn + Icpp - Icnp - Icpn)/4,
+                izz = Iccn + Iccp - 2*Iccc,
+                veloc = (Tfloat)(G(x,y,z,0)*ixx + 2*G(x,y,z,1)*ixy + 2*G(x,y,z,2)*ixz + G(x,y,z,3)*iyy + 2*G(x,y,z,4)*iyz + G(x,y,z,5)*izz);
+              *(ptrd++) = veloc;
+              if (veloc>veloc_max) veloc_max = veloc; else if (-veloc>veloc_max) veloc_max = -veloc;
+            }
+          } else { // 2d version
+            CImg_3x3(I,Tfloat);
+            cimg_forZC(*this,z,c) cimg_for3x3(*this,x,y,z,c,I,Tfloat) {
+              const Tfloat
+                ixx = Inc + Ipc - 2*Icc,
+                ixy = (Inn + Ipp - Inp - Ipn)/4,
+                iyy = Icn + Icp - 2*Icc,
+                veloc = (Tfloat)(G(x,y,0,0)*ixx + 2*G(x,y,0,1)*ixy + G(x,y,0,2)*iyy);
+              *(ptrd++) = veloc;
+              if (veloc>veloc_max) veloc_max = veloc; else if (-veloc>veloc_max) veloc_max = -veloc;
+            }
+          }
+          if (veloc_max>0) *this+=(velocity*=dl/veloc_max);
+        }
+      } else { // LIC-based smoothing.
+        const unsigned int whd = _width*_height*_depth;
+        const float sqrt2amplitude = (float)std::sqrt(2*amplitude);
+        const int dx1 = width() - 1, dy1 = height() - 1, dz1 = depth() - 1;
+        CImg<Tfloat> res(_width,_height,_depth,_spectrum,0), W(_width,_height,_depth,is_3d?4:3), val(_spectrum);
+        int N = 0;
+        if (is_3d) { // 3d version
+          for (float phi = (180%(int)da)/2.0f; phi<=180; phi+=da) {
+            const float phir = (float)(phi*cimg::PI/180), datmp = (float)(da/std::cos(phir)), da2 = datmp<1?360.0f:datmp;
+            for (float theta = 0; theta<360; (theta+=da2),++N) {
+              const float
+                thetar = (float)(theta*cimg::PI/180),
+                vx = (float)(std::cos(thetar)*std::cos(phir)),
+                vy = (float)(std::sin(thetar)*std::cos(phir)),
+                vz = (float)std::sin(phir);
+              const t
+                *pa = G.data(0,0,0,0), *pb = G.data(0,0,0,1), *pc = G.data(0,0,0,2),
+                *pd = G.data(0,0,0,3), *pe = G.data(0,0,0,4), *pf = G.data(0,0,0,5);
+              Tfloat *pd0 = W.data(0,0,0,0), *pd1 = W.data(0,0,0,1), *pd2 = W.data(0,0,0,2), *pd3 = W.data(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)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;
+              }
+
+              Tfloat *ptrd = res._data;
+              cimg_forXYZ(*this,x,y,z) {
+                val.fill(0);
+                const float
+                  n = (float)W(x,y,z,3),
+                  fsigma = (float)(n*sqrt2amplitude),
+                  fsigma2 = 2*fsigma*fsigma,
+                  length = gauss_prec*fsigma;
+                float
+                  S = 0,
+                  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);
+                    const float
+                      u = (float)W(cx,cy,cz,0),
+                      v = (float)W(cx,cy,cz,1),
+                      w = (float)W(cx,cy,cz,2);
+                    if (fast_approx) { cimg_forC(*this,c) val[c]+=(Tfloat)(*this)(cx,cy,cz,c); ++S; }
+                    else {
+                      const float coef = (float)std::exp(-l*l/fsigma2);
+                      cimg_forC(*this,c) val[c]+=(Tfloat)(coef*(*this)(cx,cy,cz,c));
+                      S+=coef;
+                    }
+                    X+=u; Y+=v; Z+=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 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 (fast_approx) { cimg_forC(*this,c) val[c]+=(Tfloat)_linear_atXYZ(X,Y,Z,c); ++S; }
+                    else {
+                      const float coef = (float)std::exp(-l*l/fsigma2);
+                      cimg_forC(*this,c) val[c]+=(Tfloat)(coef*_linear_atXYZ(X,Y,Z,c));
+                      S+=coef;
+                    }
+                    X+=u; Y+=v; Z+=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 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)),
+                      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 (fast_approx) { cimg_forC(*this,c) val[c]+=(Tfloat)_linear_atXYZ(X,Y,Z,c); ++S; }
+                    else {
+                      const float coef = (float)std::exp(-l*l/fsigma2);
+                      cimg_forC(*this,c) val[c]+=(Tfloat)(coef*_linear_atXYZ(X,Y,Z,c));
+                      S+=coef;
+                    }
+                    X+=u; Y+=v; Z+=w;
+                  }
+                } break;
+                }
+                Tfloat *_ptrd = ptrd++;
+                if (S>0) cimg_forC(res,c) { *_ptrd+=val[c]/S; _ptrd+=whd; }
+                else cimg_forC(res,c) { *_ptrd+=(Tfloat)((*this)(x,y,z,c)); _ptrd+=whd; }
+              }
+            }
+          }
+        } else { // 2d LIC algorithm
+          for (float theta = (360%(int)da)/2.0f; theta<360; (theta+=da),++N) {
+            const float thetar = (float)(theta*cimg::PI/180), vx = (float)(std::cos(thetar)), vy = (float)(std::sin(thetar));
+            const t *pa = G.data(0,0,0,0), *pb = G.data(0,0,0,1), *pc = G.data(0,0,0,2);
+            Tfloat *pd0 = W.data(0,0,0,0), *pd1 = W.data(0,0,0,1), *pd2 = W.data(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)std::sqrt(1e-5+u*u+v*v),
+                dln = dl/n;
+              *(pd0++) = (Tfloat)(u*dln);
+              *(pd1++) = (Tfloat)(v*dln);
+              *(pd2++) = (Tfloat)n;
+            }
+            Tfloat *ptrd = res._data;
+            cimg_forXY(*this,x,y) {
+              val.fill(0);
+              const float
+                n = (float)W(x,y,0,2),
+                fsigma = (float)(n*sqrt2amplitude),
+                fsigma2 = 2*fsigma*fsigma,
+                length = gauss_prec*fsigma;
+              float
+                S = 0,
+                X = (float)x,
+                Y = (float)y;
+              switch (interpolation_type) {
+              case 0 : { // Nearest-neighbor
+                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);
+                  const float
+                    u = (float)W(cx,cy,0,0),
+                    v = (float)W(cx,cy,0,1);
+                  if (fast_approx) { cimg_forC(*this,c) val[c]+=(Tfloat)(*this)(cx,cy,0,c); ++S; }
+                  else {
+                    const float coef = (float)std::exp(-l*l/fsigma2);
+                    cimg_forC(*this,c) val[c]+=(Tfloat)(coef*(*this)(cx,cy,0,c));
+                    S+=coef;
+                  }
+                  X+=u; Y+=v;
+                }
+              } break;
+              case 1 : { // Linear interpolation
+                for (float l = 0; l<length && X>=0 && X<=dx1 && Y>=0 && Y<=dy1; l+=dl) {
+                  const float
+                    u = (float)(W._linear_atXY(X,Y,0,0)),
+                    v = (float)(W._linear_atXY(X,Y,0,1));
+                  if (fast_approx) { cimg_forC(*this,c) val[c]+=(Tfloat)_linear_atXY(X,Y,0,c); ++S; }
+                  else {
+                    const float coef = (float)std::exp(-l*l/fsigma2);
+                    cimg_forC(*this,c) val[c]+=(Tfloat)(coef*_linear_atXY(X,Y,0,c));
+                    S+=coef;
+                  }
+                  X+=u; Y+=v;
+                }
+              } break;
+              default : { // 2nd-order Runge-kutta interpolation
+                for (float l = 0; l<length && X>=0 && X<=dx1 && Y>=0 && Y<=dy1; l+=dl) {
+                  const float
+                    u0 = (float)(0.5f*W._linear_atXY(X,Y,0,0)),
+                    v0 = (float)(0.5f*W._linear_atXY(X,Y,0,1)),
+                    u = (float)(W._linear_atXY(X+u0,Y+v0,0,0)),
+                    v = (float)(W._linear_atXY(X+u0,Y+v0,0,1));
+                  if (fast_approx) { cimg_forC(*this,c) val[c]+=(Tfloat)_linear_atXY(X,Y,0,c); ++S; }
+                  else {
+                    const float coef = (float)std::exp(-l*l/fsigma2);
+                    cimg_forC(*this,c) val[c]+=(Tfloat)(coef*_linear_atXY(X,Y,0,c));
+                    S+=coef;
+                  }
+                  X+=u; Y+=v;
+                }
+              }
+              }
+              Tfloat *_ptrd = ptrd++;
+              if (S>0) cimg_forC(res,c) { *_ptrd+=val[c]/S; _ptrd+=whd; }
+              else cimg_forC(res,c) { *_ptrd+=(Tfloat)((*this)(x,y,0,c)); _ptrd+=whd; }
+            }
+          }
+        }
+        const Tfloat *ptrs = res.end();
+        cimg_for(*this,ptrd,T) { const Tfloat val = *(--ptrs)/N; *ptrd = val<val_min?val_min:(val>val_max?val_max:(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 following in an anisotropic way.
+    CImg<T>& blur_anisotropic(const float amplitude, const float sharpness=0.7f, const float anisotropy=0.6f,
+                              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) {
+      return blur_anisotropic(get_edge_tensors(sharpness,anisotropy,alpha,sigma,interpolation_type!=3),
+                              amplitude,dl,da,gauss_prec,interpolation_type,fast_approx);
+    }
+
+    CImg<T> get_blur_anisotropic(const float amplitude, const float sharpness=0.7f, const float anisotropy=0.6f,
+                                 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 {
+      return (+*this).blur_anisotropic(amplitude,sharpness,anisotropy,alpha,sigma,dl,da,gauss_prec,interpolation_type,fast_approx);
+    }
+
+    //! Blur an image using the bilateral filter.
+    /**
+       \param sigma_x Amount of blur along the X-axis.
+       \param sigma_y Amount of blur along the Y-axis.
+       \param sigma_z Amount of blur along the Z-axis.
+       \param sigma_r Amount of blur along the range axis.
+       \param bgrid_x Size of the bilateral grid along the X-axis.
+       \param bgrid_y Size of the bilateral grid along the Y-axis.
+       \param bgrid_z Size of the bilateral grid along the Z-axis.
+       \param bgrid_r 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 sigma_x, const float sigma_y, const float sigma_z, const float sigma_r,
+                            const int bgrid_x, const int bgrid_y, const int bgrid_z, const int bgrid_r,
+                            const bool interpolation_type=true) {
+      if (is_empty()) return *this;
+      T m, M = max_min(m);
+      const float range = (float)(1.0f + M - m);
+      const unsigned int
+        bx0 = bgrid_x>=0?bgrid_x:_width*(-bgrid_x)/100,
+        by0 = bgrid_y>=0?bgrid_y:_height*(-bgrid_y)/100,
+        bz0 = bgrid_z>=0?bgrid_z:_depth*(-bgrid_z)/100,
+        br0 = bgrid_r>=0?bgrid_r:(int)(-range*bgrid_r/100),
+        bx = bx0>0?bx0:1,
+        by = by0>0?by0:1,
+        bz = bz0>0?bz0:1,
+        br = br0>0?br0:1;
+      const float
+        _sigma_x = sigma_x>=0?sigma_x:-sigma_x*_width/100,
+        _sigma_y = sigma_y>=0?sigma_y:-sigma_y*_height/100,
+        _sigma_z = sigma_z>=0?sigma_z:-sigma_z*_depth/100,
+        nsigma_x = _sigma_x*bx/_width,
+        nsigma_y = _sigma_y*by/_height,
+        nsigma_z = _sigma_z*bz/_depth,
+        nsigma_r = sigma_r*br/range;
+      if (nsigma_x>0 || nsigma_y>0 || nsigma_z>0 || nsigma_r>0) {
+        const bool is_3d = (_depth>1);
+        if (is_3d) { // 3d version of the algorithm
+          CImg<floatT> bgrid(bx,by,bz,br), bgridw(bx,by,bz,br);
+          cimg_forC(*this,c) {
+            bgrid.fill(0); bgridw.fill(0);
+            cimg_forXYZ(*this,x,y,z) {
+              const T val = (*this)(x,y,z,c);
+              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(nsigma_x,nsigma_y,nsigma_z,true).deriche(nsigma_r,0,'c',false);
+            bgridw.blur(nsigma_x,nsigma_y,nsigma_z,true).deriche(nsigma_r,0,'c',false);
+            if (interpolation_type) cimg_forXYZ(*this,x,y,z) {
+              const T val = (*this)(x,y,z,c);
+              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_atXYZC(X,Y,Z,R), bval1 = bgridw._linear_atXYZC(X,Y,Z,R);
+              (*this)(x,y,z,c) = (T)(bval0/bval1);
+            } else cimg_forXYZ(*this,x,y,z) {
+              const T val = (*this)(x,y,z,c);
+              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,c) = (T)(bval0/bval1);
+            }
+          }
+        } else { // 2d version of the algorithm
+          CImg<floatT> bgrid(bx,by,br,2);
+          cimg_forC(*this,c) {
+            bgrid.fill(0);
+            cimg_forXY(*this,x,y) {
+              const T val = (*this)(x,y,c);
+              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(nsigma_x,nsigma_y,0,true).blur(0,0,nsigma_r,false);
+            if (interpolation_type) cimg_forXY(*this,x,y) {
+              const T val = (*this)(x,y,c);
+              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,c) = (T)(bval0/bval1);
+            } else cimg_forXY(*this,x,y) {
+              const T val = (*this)(x,y,c);
+              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,c) = (T)(bval0/bval1);
+            }
+          }
+        }
+      }
+      return *this;
+    }
+
+    CImg<T> get_blur_bilateral(const float sigma_x, const float sigma_y, const float sigma_z, const float sigma_r,
+                               const int bgrid_x, const int bgrid_y, const int bgrid_z, const int bgrid_r,
+                               const bool interpolation_type=true) const {
+      return (+*this).blur_bilateral(sigma_x,sigma_y,sigma_z,sigma_r,bgrid_x,bgrid_y,bgrid_z,bgrid_r,interpolation_type);
+    }
+
+    //! Blur an image using the bilateral filter.
+    CImg<T>& blur_bilateral(const float sigma_s, const float sigma_r, const int bgrid_s=-33, const int bgrid_r=32,
+                            const bool interpolation_type=true) {
+      const float nsigma_s = sigma_s>=0?sigma_s:-sigma_s*cimg::max(_width,_height,_depth)/100;
+      return blur_bilateral(nsigma_s,nsigma_s,nsigma_s,sigma_r,bgrid_s,bgrid_s,bgrid_s,bgrid_r,interpolation_type);
+    }
+
+    CImg<T> get_blur_bilateral(const float sigma_s, const float sigma_r, const int bgrid_s=-33, const int bgrid_r=32,
+                               const bool interpolation_type=true) const {
+      return (+*this).blur_bilateral(sigma_s,sigma_s,sigma_s,sigma_r,bgrid_s,bgrid_s,bgrid_s,bgrid_r,interpolation_type);
+    }
+
+    //! Blur an image in its patch-based space.
+    CImg<T>& blur_patch(const float sigma_s, const float sigma_p, const unsigned int patch_size=3,
+                        const unsigned int lookup_size=4, const float smoothness=0, const bool fast_approx=true) {
+      if (is_empty() || !patch_size || !lookup_size) return *this;
+      return get_blur_patch(sigma_s,sigma_p,patch_size,lookup_size,smoothness,fast_approx).move_to(*this);
+    }
+
+    CImg<T> get_blur_patch(const float sigma_s, const float sigma_p, const unsigned int patch_size=3,
+                           const unsigned int lookup_size=4, const float smoothness=0, const bool fast_approx=true) const {
+
+#define _cimg_blur_patch3d_fast(N) \
+      cimg_for##N##XYZ(res,x,y,z) { \
+        T *pP = P._data; cimg_forC(res,c) { cimg_get##N##x##N##x##N(img,x,y,z,c,pP,T); pP+=N3; } \
+        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(res,x0,y0,z0,x1,y1,z1,p,q,r) if (cimg::abs(img(x,y,z,0) - img(p,q,r,0))<sigma_p3) { \
+          T *pQ = Q._data; cimg_forC(res,c) { cimg_get##N##x##N##x##N(img,p,q,r,c,pQ,T); pQ+=N3; } \
+          float distance2 = 0; \
+          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 = alldist>3?0.0f:1.0f; \
+          sum_weights+=weight; \
+          cimg_forC(res,c) res(x,y,z,c)+=weight*(*this)(p,q,r,c); \
+        } \
+        if (sum_weights>0) cimg_forC(res,c) res(x,y,z,c)/=sum_weights; \
+        else cimg_forC(res,c) res(x,y,z,c) = (Tfloat)((*this)(x,y,z,c)); \
+    }
+
+#define _cimg_blur_patch3d(N) \
+      cimg_for##N##XYZ(res,x,y,z) { \
+        T *pP = P._data; cimg_forC(res,c) { cimg_get##N##x##N##x##N(img,x,y,z,c,pP,T); pP+=N3; } \
+        const int x0 = x - rsize1, y0 = y - rsize1, z0 = z - rsize1, x1 = x + rsize2, y1 = y + rsize2, z1 = z + rsize2; \
+        float sum_weights = 0, weight_max = 0; \
+        cimg_for_in##N##XYZ(res,x0,y0,z0,x1,y1,z1,p,q,r) if (p!=x || q!=y || r!=z) { \
+          T *pQ = Q._data; cimg_forC(res,c) { cimg_get##N##x##N##x##N(img,p,q,r,c,pQ,T); pQ+=N3; } \
+          float distance2 = 0; \
+          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)std::exp(-alldist); \
+          if (weight>weight_max) weight_max = weight; \
+          sum_weights+=weight; \
+          cimg_forC(res,c) res(x,y,z,c)+=weight*(*this)(p,q,r,c); \
+        } \
+        sum_weights+=weight_max; cimg_forC(res,c) res(x,y,z,c)+=weight_max*(*this)(x,y,z,c); \
+        if (sum_weights>0) cimg_forC(res,c) res(x,y,z,c)/=sum_weights; \
+        else cimg_forC(res,c) res(x,y,z,c) = (Tfloat)((*this)(x,y,z,c)); \
+      }
+
+#define _cimg_blur_patch2d_fast(N) \
+        cimg_for##N##XY(res,x,y) { \
+          T *pP = P._data; cimg_forC(res,c) { cimg_get##N##x##N(img,x,y,0,c,pP,T); pP+=N2; } \
+          const int x0 = x - rsize1, y0 = y - rsize1, x1 = x + rsize2, y1 = y + rsize2; \
+          float sum_weights = 0; \
+          cimg_for_in##N##XY(res,x0,y0,x1,y1,p,q) if (cimg::abs(img(x,y,0,0) - img(p,q,0,0))<sigma_p3) { \
+            T *pQ = Q._data; cimg_forC(res,c) { cimg_get##N##x##N(img,p,q,0,c,pQ,T); pQ+=N2; } \
+            float distance2 = 0; \
+            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 = alldist>3?0.0f:1.0f; \
+            sum_weights+=weight; \
+            cimg_forC(res,c) res(x,y,c)+=weight*(*this)(p,q,c); \
+          } \
+          if (sum_weights>0) cimg_forC(res,c) res(x,y,c)/=sum_weights; \
+          else cimg_forC(res,c) res(x,y,c) = (Tfloat)((*this)(x,y,c)); \
+        }
+
+#define _cimg_blur_patch2d(N) \
+        cimg_for##N##XY(res,x,y) { \
+          T *pP = P._data; cimg_forC(res,c) { cimg_get##N##x##N(img,x,y,0,c,pP,T); pP+=N2; } \
+          const int x0 = x - rsize1, y0 = y - rsize1, x1 = x + rsize2, y1 = y + rsize2; \
+          float sum_weights = 0, weight_max = 0; \
+          cimg_for_in##N##XY(res,x0,y0,x1,y1,p,q) if (p!=x || q!=y) { \
+            T *pQ = Q._data; cimg_forC(res,c) { cimg_get##N##x##N(img,p,q,0,c,pQ,T); pQ+=N2; } \
+            float distance2 = 0; \
+            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)std::exp(-alldist); \
+            if (weight>weight_max) weight_max = weight; \
+            sum_weights+=weight; \
+            cimg_forC(res,c) res(x,y,c)+=weight*(*this)(p,q,c); \
+          } \
+          sum_weights+=weight_max; cimg_forC(res,c) res(x,y,c)+=weight_max*(*this)(x,y,c); \
+          if (sum_weights>0) cimg_forC(res,c) res(x,y,c)/=sum_weights; \
+          else cimg_forC(res,c) res(x,y,c) = (Tfloat)((*this)(x,y,c)); \
+    }
+
+      if (is_empty() || !patch_size || !lookup_size) return +*this;
+      CImg<Tfloat> res(_width,_height,_depth,_spectrum,0);
+      const CImg<T> _img = smoothness>0?get_blur(smoothness):CImg<Tfloat>(),&img = smoothness>0?_img:*this;
+      CImg<T> P(patch_size*patch_size*_spectrum), Q(P);
+      const float
+        nsigma_s = sigma_s>=0?sigma_s:-sigma_s*cimg::max(_width,_height,_depth)/100,
+        sigma_s2 = nsigma_s*nsigma_s, sigma_p2 = sigma_p*sigma_p, sigma_p3 = 3*sigma_p,
+        Pnorm = P.size()*sigma_p2;
+      const int rsize2 = (int)lookup_size/2, rsize1 = (int)lookup_size - rsize2 - 1;
+      const unsigned int N2 = patch_size*patch_size, N3 = N2*patch_size;
+      if (_depth>1) switch (patch_size) { // 3d
+      case 2 : if (fast_approx) _cimg_blur_patch3d_fast(2) else _cimg_blur_patch3d(2) break;
+      case 3 : if (fast_approx) _cimg_blur_patch3d_fast(3) else _cimg_blur_patch3d(3) break;
+      default : {
+        const int psize2 = (int)patch_size/2, psize1 = (int)patch_size - psize2 - 1;
+        if (fast_approx) cimg_forXYZ(res,x,y,z) { // Fast
+          P = img.get_crop(x - psize1,y - psize1,z - psize1,x + psize2,y + psize2,z + psize2,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(res,x0,y0,z0,x1,y1,z1,p,q,r) if (cimg::abs(img(x,y,z,0)-img(p,q,r,0))<sigma_p3) {
+            (Q = img.get_crop(p - psize1,q - psize1,r - psize1,p + psize2,q + psize2,r + psize2,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 = distance2>3?0.0f:1.0f;
+            sum_weights+=weight;
+            cimg_forC(res,c) res(x,y,z,c)+=weight*(*this)(p,q,r,c);
+          }
+          if (sum_weights>0) cimg_forC(res,c) res(x,y,z,c)/=sum_weights;
+          else cimg_forC(res,c) res(x,y,z,c) = (Tfloat)((*this)(x,y,z,c));
+        } else cimg_forXYZ(res,x,y,z) { // Exact
+          P = img.get_crop(x - psize1,y - psize1,z - psize1,x + psize2,y + psize2,z + psize2,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, weight_max = 0;
+          cimg_for_inXYZ(res,x0,y0,z0,x1,y1,z1,p,q,r) if (p!=x || q!=y || r!=z) {
+            (Q = img.get_crop(p - psize1,q - psize1,r - psize1,p + psize2,q + psize2,r + psize2,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)std::exp(-distance2);
+            if (weight>weight_max) weight_max = weight;
+            sum_weights+=weight;
+            cimg_forC(res,c) res(x,y,z,c)+=weight*(*this)(p,q,r,c);
+          }
+          sum_weights+=weight_max; cimg_forC(res,c) res(x,y,z,c)+=weight_max*(*this)(x,y,z,c);
+          if (sum_weights>0) cimg_forC(res,c) res(x,y,z,c)/=sum_weights;
+          else cimg_forC(res,c) res(x,y,z,c) = (Tfloat)((*this)(x,y,z,c));
+        }
+      }
+      } else switch (patch_size) { // 2d
+      case 2 : if (fast_approx) _cimg_blur_patch2d_fast(2) else _cimg_blur_patch2d(2) break;
+      case 3 : if (fast_approx) _cimg_blur_patch2d_fast(3) else _cimg_blur_patch2d(3) break;
+      case 4 : if (fast_approx) _cimg_blur_patch2d_fast(4) else _cimg_blur_patch2d(4) break;
+      case 5 : if (fast_approx) _cimg_blur_patch2d_fast(5) else _cimg_blur_patch2d(5) break;
+      case 6 : if (fast_approx) _cimg_blur_patch2d_fast(6) else _cimg_blur_patch2d(6) break;
+      case 7 : if (fast_approx) _cimg_blur_patch2d_fast(7) else _cimg_blur_patch2d(7) break;
+      case 8 : if (fast_approx) _cimg_blur_patch2d_fast(8) else _cimg_blur_patch2d(8) break;
+      case 9 : if (fast_approx) _cimg_blur_patch2d_fast(9) else _cimg_blur_patch2d(9) break;
+      default : { // Fast
+        const int psize2 = (int)patch_size/2, psize1 = (int)patch_size - psize2 - 1;
+        if (fast_approx) cimg_forXY(res,x,y) { // 2d fast approximation.
+          P = img.get_crop(x - psize1,y - psize1,x + psize2,y + psize2,true);
+          const int x0 = x - rsize1, y0 = y - rsize1, x1 = x + rsize2, y1 = y + rsize2;
+          float sum_weights = 0;
+          cimg_for_inXY(res,x0,y0,x1,y1,p,q) if (cimg::abs(img(x,y,0)-img(p,q,0))<sigma_p3) {
+            (Q = img.get_crop(p - psize1,q - psize1,p + psize2,q + psize2,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 = distance2>3?0.0f:1.0f;
+            sum_weights+=weight;
+            cimg_forC(res,c) res(x,y,c)+=weight*(*this)(p,q,c);
+          }
+          if (sum_weights>0) cimg_forC(res,c) res(x,y,c)/=sum_weights;
+          else cimg_forC(res,c) res(x,y,c) = (Tfloat)((*this)(x,y,c));
+        } else cimg_forXY(res,x,y) { // 2d exact algorithm.
+          P = img.get_crop(x - psize1,y - psize1,x + psize2,y + psize2,true);
+          const int x0 = x - rsize1, y0 = y - rsize1, x1 = x + rsize2, y1 = y + rsize2;
+          float sum_weights = 0, weight_max = 0;
+          cimg_for_inXY(res,x0,y0,x1,y1,p,q) if (p!=x || q!=y) {
+            (Q = img.get_crop(p - psize1,q - psize1,p + psize2,q + psize2,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)std::exp(-distance2);
+            if (weight>weight_max) weight_max = weight;
+            sum_weights+=weight;
+            cimg_forC(res,c) res(x,y,c)+=weight*(*this)(p,q,c);
+          }
+          sum_weights+=weight_max; cimg_forC(res,c) res(x,y,c)+=weight_max*(*this)(x,y,c);
+          if (sum_weights>0) cimg_forC(res,c) res(x,y,c)/=sum_weights;
+          else cimg_forC(res,c) res(x,y,0,c) = (Tfloat)((*this)(x,y,c));
+        }
+      }
+      }
+      return res;
+    }
+
+    //! Apply a median filter.
+    CImg<T>& blur_median(const unsigned int n) {
+      if (!n) return *this;
+      return get_blur_median(n).move_to(*this);
+    }
+
+    CImg<T> get_blur_median(const unsigned int n) const {
+      if (is_empty() || n<=1) return +*this;
+      CImg<T> res(_width,_height,_depth,_spectrum);
+      T *ptrd = res._data;
+      const int hl = n/2, hr = hl - 1 + n%2;
+      if (res._depth!=1) cimg_forXYZC(*this,x,y,z,c) { // 3d
+        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>=width()?width()-1:x1, ny1 = y1>=height()?height()-1:y1, nz1 = z1>=depth()?depth()-1:z1;
+        *(ptrd++) = get_crop(nx0,ny0,nz0,c,nx1,ny1,nz1,c).median();
+      } else {
+#define _cimg_median_sort(a,b) if ((a)>(b)) cimg::swap(a,b)
+        if (res._height!=1) switch (n) { // 2d
+        case 3 : {
+          T I[9] = { 0 };
+          CImg_3x3(J,T);
+          cimg_forC(*this,c) cimg_for3x3(*this,x,y,0,c,I,T) {
+            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);
+            *(ptrd++) = Jcc;
+          }
+        } break;
+        case 5 : {
+          T I[25] = { 0 };
+          CImg_5x5(J,T);
+          cimg_forC(*this,c) cimg_for5x5(*this,x,y,0,c,I,T) {
+            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);
+            *(ptrd++) = Jcc;
+          }
+        } break;
+        default : {
+          cimg_forXYC(*this,x,y,c) {
+            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>=width()?width()-1:x1, ny1 = y1>=height()?height()-1:y1;
+            *(ptrd++) = get_crop(nx0,ny0,0,c,nx1,ny1,0,c).median();
+          }
+        }
+        } else switch (n) { // 1d
+        case 2 : {
+          T I[4] = { 0 };
+          cimg_forC(*this,c) cimg_for2x2(*this,x,y,0,c,I,T) *(ptrd++) = (T)(0.5f*(I[0]+I[1]));
+        } break;
+        case 3 : {
+          T I[9] = { 0 };
+          cimg_forC(*this,c) cimg_for3x3(*this,x,y,0,c,I,T)
+            *(ptrd++) = 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_forXC(*this,x,c) {
+            const int
+              x0 = x - hl, x1 = x + hr,
+              nx0 = x0<0?0:x0, nx1 = x1>=width()?width()-1:x1;
+            *(ptrd++) = get_crop(nx0,0,0,c,nx1,0,0,c).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 val_min, val_max = max_min(val_min);
+      const float nedge = edge/2;
+      CImg<Tfloat> val, vec, velocity(_width,_height,_depth,_spectrum);
+      Tfloat *ptrd = velocity._data, veloc_max = 0;
+
+      if (_depth>1) { // 3d
+        CImg_3x3x3(I,Tfloat);
+        if (sharpen_type) { // Shock filters.
+          CImg<Tfloat> G = (alpha>0?get_blur(alpha).get_structure_tensors():get_structure_tensors());
+          if (sigma>0) G.blur(sigma);
+          Tfloat *ptrG0 = G.data(0,0,0,0), *ptrG1 = G.data(0,0,0,1), *ptrG2 = G.data(0,0,0,2), *ptrG3 = G.data(0,0,0,3);
+          cimg_forXYZ(G,x,y,z) {
+            G.get_tensor_at(x,y,z).symmetric_eigen(val,vec);
+            if (val[0]<0) val[0] = 0;
+            if (val[1]<0) val[1] = 0;
+            if (val[2]<0) val[2] = 0;
+            *(ptrG0++) = vec(0,0);
+            *(ptrG1++) = vec(0,1);
+            *(ptrG2++) = vec(0,2);
+            *(ptrG3++) = 1 - (Tfloat)std::pow(1+val[0]+val[1]+val[2],-(Tfloat)nedge);
+          }
+          cimg_forC(*this,c) cimg_for3x3x3(*this,x,y,z,c,I,Tfloat) {
+            const Tfloat
+              u = G(x,y,z,0),
+              v = G(x,y,z,1),
+              w = G(x,y,z,2),
+              amp = G(x,y,z,3),
+              ixx = Incc + Ipcc - 2*Iccc,
+              ixy = (Innc + Ippc - Inpc - Ipnc)/4,
+              ixz = (Incn + Ipcp - Incp - Ipcn)/4,
+              iyy = Icnc + Icpc - 2*Iccc,
+              iyz = (Icnn + Icpp - Icnp - Icpn)/4,
+              izz = Iccn + Iccp - 2*Iccc,
+              ixf = Incc - Iccc,
+              ixb = Iccc - Ipcc,
+              iyf = Icnc - Iccc,
+              iyb = Iccc - Icpc,
+              izf = Iccn - Iccc,
+              izb = Iccc - Iccp,
+              itt = u*u*ixx + v*v*iyy + w*w*izz + 2*u*v*ixy + 2*u*w*ixz + 2*v*w*iyz,
+              it = u*cimg::minmod(ixf,ixb) + v*cimg::minmod(iyf,iyb) + w*cimg::minmod(izf,izb),
+              veloc = -amp*cimg::sign(itt)*cimg::abs(it);
+            *(ptrd++) = veloc;
+            if (veloc>veloc_max) veloc_max = veloc; else if (-veloc>veloc_max) veloc_max = -veloc;
+          }
+        } else cimg_forC(*this,c) cimg_for3x3x3(*this,x,y,z,c,I,Tfloat) { // Inverse diffusion.
+          const Tfloat veloc = -Ipcc - Incc - Icpc - Icnc - Iccp - Iccn + 6*Iccc;
+          *(ptrd++) = veloc;
+          if (veloc>veloc_max) veloc_max = veloc; else if (-veloc>veloc_max) veloc_max = -veloc;
+        }
+      } else {
+        CImg_3x3(I,Tfloat);
+        if (sharpen_type) { // Shock filters.
+          CImg<Tfloat> G = (alpha>0?get_blur(alpha).get_structure_tensors():get_structure_tensors());
+          if (sigma>0) G.blur(sigma);
+          Tfloat *ptrG0 = G.data(0,0,0,0), *ptrG1 = G.data(0,0,0,1), *ptrG2 = G.data(0,0,0,2);
+          cimg_forXY(G,x,y) {
+            G.get_tensor_at(x,y).symmetric_eigen(val,vec);
+            if (val[0]<0) val[0] = 0;
+            if (val[1]<0) val[1] = 0;
+            *(ptrG0++) = vec(0,0);
+            *(ptrG1++) = vec(0,1);
+            *(ptrG2++) = 1 - (Tfloat)std::pow(1 + val[0] + val[1],-(Tfloat)nedge);
+          }
+          cimg_forC(*this,c) cimg_for3x3(*this,x,y,0,c,I,Tfloat) {
+            const Tfloat
+              u = G(x,y,0),
+              v = G(x,y,1),
+              amp = G(x,y,2),
+              ixx = Inc + Ipc - 2*Icc,
+              ixy = (Inn + Ipp - Inp - Ipn)/4,
+              iyy = Icn + Icp - 2*Icc,
+              ixf = Inc - Icc,
+              ixb = Icc - Ipc,
+              iyf = Icn - Icc,
+              iyb = Icc - Icp,
+              itt = u*u*ixx + v*v*iyy + 2*u*v*ixy,
+              it = u*cimg::minmod(ixf,ixb) + v*cimg::minmod(iyf,iyb),
+              veloc = -amp*cimg::sign(itt)*cimg::abs(it);
+            *(ptrd++) = veloc;
+            if (veloc>veloc_max) veloc_max = veloc; else if (-veloc>veloc_max) veloc_max = -veloc;
+          }
+        } else cimg_forC(*this,c) cimg_for3x3(*this,x,y,0,c,I,Tfloat) { // Inverse diffusion.
+          const Tfloat veloc = -Ipc - Inc - Icp - Icn + 4*Icc;
+          *(ptrd++) = veloc;
+          if (veloc>veloc_max) veloc_max = veloc; else if (-veloc>veloc_max) veloc_max = -veloc;
+        }
+      }
+      if (veloc_max<=0) return *this;
+      return ((velocity*=amplitude/veloc_max)+=*this).cut(val_min,val_max).move_to(*this);
+    }
+
+    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 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,_spectrum);
+      Tfloat *ptrd0 = grad[0]._data, *ptrd1 = grad[1]._data;
+      bool is_3d = 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' : is_3d = true; break;
+          default :
+            throw CImgArgumentException(_cimg_instance
+                                        "get_gradient() : Invalid specified axis '%c'.",
+                                        cimg_instance,
+                                        axis);
+          }
+        }
+      } else is_3d = (_depth>1);
+      if (is_3d) {
+        CImg<Tfloat>(_width,_height,_depth,_spectrum).move_to(grad);
+        Tfloat *ptrd2 = grad[2]._data;
+        switch (scheme) { // 3d.
+        case -1 : { // Backward finite differences.
+          CImg_3x3x3(I,Tfloat);
+          cimg_forC(*this,c) cimg_for3x3x3(*this,x,y,z,c,I,Tfloat) {
+            *(ptrd0++) = Iccc - Ipcc;
+            *(ptrd1++) = Iccc - Icpc;
+            *(ptrd2++) = Iccc - Iccp;
+          }
+        } break;
+        case 1 : { // Forward finite differences.
+          CImg_2x2x2(I,Tfloat);
+          cimg_forC(*this,c) cimg_for2x2x2(*this,x,y,z,c,I,Tfloat) {
+            *(ptrd0++) = Incc - Iccc;
+            *(ptrd1++) = Icnc - Iccc;
+            *(ptrd2++) = 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,Tfloat);
+          cimg_forC(*this,c) cimg_for3x3x3(*this,x,y,z,c,I,Tfloat) {
+            *(ptrd0++) = (Incc - Ipcc)/2;
+            *(ptrd1++) = (Icnc - Icpc)/2;
+            *(ptrd2++) = (Iccn - Iccp)/2;
+          }
+        }
+        }
+      } else switch (scheme) { // 2d.
+      case -1 : { // Backward finite differences.
+        CImg_3x3(I,Tfloat);
+        cimg_forZC(*this,z,c) cimg_for3x3(*this,x,y,z,c,I,Tfloat) {
+          *(ptrd0++) = Icc - Ipc;
+          *(ptrd1++) = Icc - Icp;
+        }
+      } break;
+      case 1 : { // Forward finite differences.
+        CImg_2x2(I,Tfloat);
+        cimg_forZC(*this,z,c) cimg_for2x2(*this,x,y,z,c,I,Tfloat) {
+          *(ptrd0++) = Inc - Icc;
+          *(ptrd1++) = Icn - Icc;
+        }
+      } break;
+      case 2 : { // Sobel scheme.
+        CImg_3x3(I,Tfloat);
+        const Tfloat a = 1, b = 2;
+        cimg_forZC(*this,z,c) cimg_for3x3(*this,x,y,z,c,I,Tfloat) {
+          *(ptrd0++) = -a*Ipp - b*Ipc - a*Ipn + a*Inp + b*Inc + a*Inn;
+          *(ptrd1++) = -a*Ipp - b*Icp - a*Inp + a*Ipn + b*Icn + a*Inn;
+        }
+      } break;
+      case 3 : { // Rotation invariant mask.
+        CImg_3x3(I,Tfloat);
+        const Tfloat a = (Tfloat)(0.25f*(2-std::sqrt(2.0f))), b = (Tfloat)(0.5f*(std::sqrt(2.0f)-1));
+        cimg_forZC(*this,z,c) cimg_for3x3(*this,x,y,z,c,I,Tfloat) {
+          *(ptrd0++) = -a*Ipp - b*Ipc - a*Ipn + a*Inp + b*Inc + a*Inn;
+          *(ptrd1++) = -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,Tfloat);
+        cimg_forZC(*this,z,c) cimg_for3x3(*this,x,y,z,c,I,Tfloat) {
+          *(ptrd0++) = (Inc - Ipc)/2;
+          *(ptrd1++) = (Icn - Icp)/2;
+        }
+      }
+      }
+      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;
+    }
+
+    //! Return components of the Hessian matrix of an image.
+    CImgList<Tfloat> get_hessian(const char *const axes=0) const {
+      CImgList<Tfloat> res;
+      const char *naxes = axes, *const def_axes2d = "xxxyyy", *const def_axes3d = "xxxyxzyyyzzz";
+      if (!axes) naxes = _depth>1?def_axes3d:def_axes2d;
+      const unsigned int lmax = std::strlen(naxes);
+      if (lmax%2)
+        throw CImgArgumentException(_cimg_instance
+                                    "get_hessian() : Invalid specified axes '%s'.",
+                                    cimg_instance,
+                                    naxes);
+
+      res.assign(lmax/2,_width,_height,_depth,_spectrum);
+      if (!cimg::strcasecmp(naxes,def_axes3d)) { // 3d
+        Tfloat
+          *ptrd0 = res[0]._data, *ptrd1 = res[1]._data, *ptrd2 = res[2]._data,
+          *ptrd3 = res[3]._data, *ptrd4 = res[4]._data, *ptrd5 = res[5]._data;
+        CImg_3x3x3(I,Tfloat);
+        cimg_forC(*this,c) cimg_for3x3x3(*this,x,y,z,c,I,Tfloat) {
+          *(ptrd0++) = Ipcc + Incc - 2*Iccc;          // Ixx
+          *(ptrd1++) = (Ippc + Innc - Ipnc - Inpc)/4; // Ixy
+          *(ptrd2++) = (Ipcp + Incn - Ipcn - Incp)/4; // Ixz
+          *(ptrd3++) = Icpc + Icnc - 2*Iccc;          // Iyy
+          *(ptrd4++) = (Icpp + Icnn - Icpn - Icnp)/4; // Iyz
+          *(ptrd5++) = Iccn + Iccp - 2*Iccc;          // Izz
+        }
+      } else if (!cimg::strcasecmp(naxes,def_axes2d)) { // 2d
+        Tfloat *ptrd0 = res[0]._data, *ptrd1 = res[1]._data, *ptrd2 = res[2]._data;
+        CImg_3x3(I,Tfloat);
+        cimg_forC(*this,c) cimg_for3x3(*this,x,y,0,c,I,Tfloat) {
+          *(ptrd0++) = Ipc + Inc - 2*Icc;         // Ixx
+          *(ptrd1++) = (Ipp + Inn - Ipn - Inp)/4; // Ixy
+          *(ptrd2++) = Icp + Icn - 2*Icc;         // Iyy
+        }
+      } else for (unsigned int l = 0; l<lmax; ) { // Version with custom axes.
+        const unsigned int l2 = l/2;
+          char axis1 = naxes[l++], axis2 = naxes[l++];
+          if (axis1>axis2) cimg::swap(axis1,axis2);
+          bool valid_axis = false;
+          Tfloat *ptrd = res[l2]._data;
+          if (axis1=='x' && axis2=='x') { // Ixx
+            valid_axis = true; CImg_3x3(I,Tfloat);
+            cimg_forZC(*this,z,c) cimg_for3x3(*this,x,y,z,c,I,Tfloat) *(ptrd++) = Ipc + Inc - 2*Icc;
+          }
+          else if (axis1=='x' && axis2=='y') { // Ixy
+            valid_axis = true; CImg_3x3(I,Tfloat);
+            cimg_forZC(*this,z,c) cimg_for3x3(*this,x,y,z,c,I,Tfloat) *(ptrd++) = (Ipp + Inn - Ipn - Inp)/4;
+          }
+          else if (axis1=='x' && axis2=='z') { // Ixz
+            valid_axis = true; CImg_3x3x3(I,Tfloat);
+            cimg_forC(*this,c) cimg_for3x3x3(*this,x,y,z,c,I,Tfloat) *(ptrd++) = (Ipcp + Incn - Ipcn - Incp)/4;
+          }
+          else if (axis1=='y' && axis2=='y') { // Iyy
+            valid_axis = true; CImg_3x3(I,Tfloat);
+            cimg_forZC(*this,z,c) cimg_for3x3(*this,x,y,z,c,I,Tfloat) *(ptrd++) = Icp + Icn - 2*Icc;
+          }
+          else if (axis1=='y' && axis2=='z') { // Iyz
+            valid_axis = true; CImg_3x3x3(I,Tfloat);
+            cimg_forC(*this,c) cimg_for3x3x3(*this,x,y,z,c,I,Tfloat) *(ptrd++) = (Icpp + Icnn - Icpn - Icnp)/4;
+          }
+          else if (axis1=='z' && axis2=='z') { // Izz
+            valid_axis = true; CImg_3x3x3(I,Tfloat);
+            cimg_forC(*this,c) cimg_for3x3x3(*this,x,y,z,c,I,Tfloat) *(ptrd++) = Iccn + Iccp - 2*Iccc;
+          }
+          else if (!valid_axis)
+            throw CImgArgumentException(_cimg_instance
+                                        "get_hessian() : Invalid specified axes '%s'.",
+                                        cimg_instance,
+                                        naxes);
+        }
+      return res;
+    }
+
+    //! Compute the laplacian of the image instance.
+    CImg<T>& laplacian() {
+      return get_laplacian().move_to(*this);
+    }
+
+    CImg<Tfloat> get_laplacian() const {
+      if (is_empty()) return CImg<Tfloat>();
+      CImg<Tfloat> res(_width,_height,_depth,_spectrum);
+      Tfloat *ptrd = res._data;
+      if (_depth>1) { // 3d
+        CImg_3x3x3(I,Tfloat);
+        cimg_forC(*this,c) cimg_for3x3x3(*this,x,y,z,c,I,Tfloat)
+          *(ptrd++) = Incc + Ipcc + Icnc + Icpc + Iccn + Iccp - 6*Iccc;
+      } else if (_height>1) { // 2d
+        CImg_3x3(I,Tfloat);
+        cimg_forC(*this,c) cimg_for3x3(*this,x,y,0,c,I,Tfloat)
+          *(ptrd++) = Inc + Ipc + Icn + Icp - 4*Icc;
+      } else { // 1d
+        CImg_3x3(I,Tfloat);
+        cimg_forC(*this,c) cimg_for3x3(*this,x,y,0,c,I,Tfloat)
+          *(ptrd++) = Inc + Ipc - 2*Icc;
+      }
+      return res;
+    }
+
+    //! Compute the structure tensor field of an image.
+    CImg<T>& structure_tensors(const unsigned int scheme=2) {
+      return get_structure_tensors(scheme).move_to(*this);
+    }
+
+    CImg<Tfloat> get_structure_tensors(const unsigned int scheme=2) const {
+      if (is_empty()) return *this;
+      CImg<Tfloat> res;
+      if (_depth>1) { // 3d
+        res.assign(_width,_height,_depth,6,0);
+        CImg_3x3x3(I,Tfloat);
+        switch (scheme) {
+        case 0 : { // classical central finite differences
+          cimg_forC(*this,c) {
+            Tfloat
+              *ptrd0 = res.data(0,0,0,0), *ptrd1 = res.data(0,0,0,1), *ptrd2 = res.data(0,0,0,2),
+              *ptrd3 = res.data(0,0,0,3), *ptrd4 = res.data(0,0,0,4), *ptrd5 = res.data(0,0,0,5);
+            cimg_for3x3x3(*this,x,y,z,c,I,Tfloat) {
+              const Tfloat
+                ix = (Incc - Ipcc)/2,
+                iy = (Icnc - Icpc)/2,
+                iz = (Iccn - Iccp)/2;
+              *(ptrd0++)+=ix*ix;
+              *(ptrd1++)+=ix*iy;
+              *(ptrd2++)+=ix*iz;
+              *(ptrd3++)+=iy*iy;
+              *(ptrd4++)+=iy*iz;
+              *(ptrd5++)+=iz*iz;
+            }
+          }
+        } break;
+        case 1 : { // Forward/backward finite differences (version 1).
+          cimg_forC(*this,c) {
+            Tfloat
+              *ptrd0 = res.data(0,0,0,0), *ptrd1 = res.data(0,0,0,1), *ptrd2 = res.data(0,0,0,2),
+              *ptrd3 = res.data(0,0,0,3), *ptrd4 = res.data(0,0,0,4), *ptrd5 = res.data(0,0,0,5);
+            cimg_for3x3x3(*this,x,y,z,c,I,Tfloat) {
+              const Tfloat
+                ixf = Incc - Iccc, ixb = Iccc - Ipcc,
+                iyf = Icnc - Iccc, iyb = Iccc - Icpc,
+                izf = Iccn - Iccc, izb = Iccc - Iccp;
+              *(ptrd0++)+=(ixf*ixf + ixf*ixb + ixb*ixf + ixb*ixb)/4;
+              *(ptrd1++)+=(ixf*iyf + ixf*iyb + ixb*iyf + ixb*iyb)/4;
+              *(ptrd2++)+=(ixf*izf + ixf*izb + ixb*izf + ixb*izb)/4;
+              *(ptrd3++)+=(iyf*iyf + iyf*iyb + iyb*iyf + iyb*iyb)/4;
+              *(ptrd4++)+=(iyf*izf + iyf*izb + iyb*izf + iyb*izb)/4;
+              *(ptrd5++)+=(izf*izf + izf*izb + izb*izf + izb*izb)/4;
+            }
+          }
+        } break;
+        default : { // Forward/backward finite differences (version 2).
+          cimg_forC(*this,c) {
+            Tfloat
+              *ptrd0 = res.data(0,0,0,0), *ptrd1 = res.data(0,0,0,1), *ptrd2 = res.data(0,0,0,2),
+              *ptrd3 = res.data(0,0,0,3), *ptrd4 = res.data(0,0,0,4), *ptrd5 = res.data(0,0,0,5);
+            cimg_for3x3x3(*this,x,y,z,c,I,Tfloat) {
+              const Tfloat
+                ixf = Incc - Iccc, ixb = Iccc - Ipcc,
+                iyf = Icnc - Iccc, iyb = Iccc - Icpc,
+                izf = Iccn - Iccc, izb = Iccc - Iccp;
+              *(ptrd0++)+=(ixf*ixf + ixb*ixb)/2;
+              *(ptrd1++)+=(ixf*iyf + ixf*iyb + ixb*iyf + ixb*iyb)/4;
+              *(ptrd2++)+=(ixf*izf + ixf*izb + ixb*izf + ixb*izb)/4;
+              *(ptrd3++)+=(iyf*iyf + iyb*iyb)/2;
+              *(ptrd4++)+=(iyf*izf + iyf*izb + iyb*izf + iyb*izb)/4;
+              *(ptrd5++)+=(izf*izf + izb*izb)/2;
+            }
+          }
+        } break;
+        }
+      } else { // 2d
+        res.assign(_width,_height,_depth,3,0);
+        CImg_3x3(I,Tfloat);
+        switch (scheme) {
+        case 0 : { // classical central finite differences
+          cimg_forC(*this,c) {
+            Tfloat *ptrd0 = res.data(0,0,0,0), *ptrd1 = res.data(0,0,0,1), *ptrd2 = res.data(0,0,0,2);
+            cimg_for3x3(*this,x,y,0,c,I,Tfloat) {
+              const Tfloat
+                ix = (Inc - Ipc)/2,
+                iy = (Icn - Icp)/2;
+              *(ptrd0++)+=ix*ix;
+              *(ptrd1++)+=ix*iy;
+              *(ptrd2++)+=iy*iy;
+            }
+          }
+        } break;
+        case 1 : { // Forward/backward finite differences (version 1).
+          cimg_forC(*this,c) {
+            Tfloat *ptrd0 = res.data(0,0,0,0), *ptrd1 = res.data(0,0,0,1), *ptrd2 = res.data(0,0,0,2);
+            cimg_for3x3(*this,x,y,0,c,I,Tfloat) {
+              const Tfloat
+                ixf = Inc - Icc, ixb = Icc - Ipc,
+                iyf = Icn - Icc, iyb = Icc - Icp;
+              *(ptrd0++)+=(ixf*ixf + ixf*ixb + ixb*iyf + ixb*ixb)/4;
+              *(ptrd1++)+=(ixf*iyf + ixf*iyb + ixb*iyf + ixb*iyb)/4;
+              *(ptrd2++)+=(iyf*iyf + iyf*iyb + iyb*iyf + iyb*iyb)/4;
+            }
+          }
+        } break;
+        default : { // Forward/backward finite differences (version 2).
+          cimg_forC(*this,c) {
+            Tfloat *ptrd0 = res.data(0,0,0,0), *ptrd1 = res.data(0,0,0,1), *ptrd2 = res.data(0,0,0,2);
+            cimg_for3x3(*this,x,y,0,c,I,Tfloat) {
+              const Tfloat
+                ixf = Inc - Icc, ixb = Icc - Ipc,
+                iyf = Icn - Icc, iyb = Icc - Icp;
+              *(ptrd0++)+=(ixf*ixf + ixb*ixb)/2;
+              *(ptrd1++)+=(ixf*iyf + ixf*iyb + ixb*iyf + ixb*iyb)/4;
+              *(ptrd2++)+=(iyf*iyf + iyb*iyb)/2;
+            }
+          }
+        } break;
+        }
+      }
+      return res;
+    }
+
+    //! Return a diffusion tensor for edge-preserving anisotropic smoothing of an image.
+    CImg<T>& edge_tensors(const float sharpness=0.7f, const float anisotropy=0.6f,
+                          const float alpha=0.6f, const float sigma=1.1f, const bool is_sqrt=false) {
+      CImg<Tfloat> res;
+      const float nsharpness = cimg::max(sharpness,1e-5f), power1 = (is_sqrt?0.5f:1)*nsharpness, power2 = power1/(1e-7f+1-anisotropy);
+      blur(alpha).normalize(0,(T)255);
+
+      if (_depth>1) { // 3d
+        CImg<floatT> val(3), vec(3,3);
+        get_structure_tensors().move_to(res).blur(sigma);
+        Tfloat
+          *ptrd0 = res.data(0,0,0,0), *ptrd1 = res.data(0,0,0,1), *ptrd2 = res.data(0,0,0,2),
+          *ptrd3 = res.data(0,0,0,3), *ptrd4 = res.data(0,0,0,4), *ptrd5 = res.data(0,0,0,5);
+        cimg_forXYZ(*this,x,y,z) {
+          res.get_tensor_at(x,y,z).symmetric_eigen(val,vec);
+          const float
+            _l1 = val[2], _l2 = val[1], _l3 = val[0],
+            l1 = _l1>0?_l1:0, l2 = _l2>0?_l2:0, l3 = _l3>0?_l3: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)std::pow(1+l1+l2+l3,-power1),
+            n2 = (float)std::pow(1+l1+l2+l3,-power2);
+          *(ptrd0++) = n1*(ux*ux + vx*vx) + n2*wx*wx;
+          *(ptrd1++) = n1*(ux*uy + vx*vy) + n2*wx*wy;
+          *(ptrd2++) = n1*(ux*uz + vx*vz) + n2*wx*wz;
+          *(ptrd3++) = n1*(uy*uy + vy*vy) + n2*wy*wy;
+          *(ptrd4++) = n1*(uy*uz + vy*vz) + n2*wy*wz;
+          *(ptrd5++) = n1*(uz*uz + vz*vz) + n2*wz*wz;
+        }
+      } else { // for 2d images
+        CImg<floatT> val(2), vec(2,2);
+        get_structure_tensors().move_to(res).blur(sigma);
+        Tfloat *ptrd0 = res.data(0,0,0,0), *ptrd1 = res.data(0,0,0,1), *ptrd2 = res.data(0,0,0,2);
+        cimg_forXY(*this,x,y) {
+          res.get_tensor_at(x,y).symmetric_eigen(val,vec);
+          const float
+            _l1 = val[1], _l2 = val[0],
+            l1 = _l1>0?_l1:0, l2 = _l2>0?_l2:0,
+            ux = vec(1,0), uy = vec(1,1),
+            vx = vec(0,0), vy = vec(0,1),
+            n1 = (float)std::pow(1+l1+l2,-power1),
+            n2 = (float)std::pow(1+l1+l2,-power2);
+          *(ptrd0++) = n1*ux*ux + n2*vx*vx;
+          *(ptrd1++) = n1*ux*uy + n2*vx*vy;
+          *(ptrd2++) = n1*uy*uy + n2*vy*vy;
+        }
+      }
+      return res.move_to(*this);
+    }
+
+    CImg<Tfloat> get_edge_tensors(const float sharpness=0.7f, const float anisotropy=0.6f,
+                                  const float alpha=0.6f, const float sigma=1.1f, const bool is_sqrt=false) const {
+      return CImg<Tfloat>(*this,false).edge_tensors(sharpness,anisotropy,alpha,sigma,is_sqrt);
+    }
+
+    //! Estimate a displacement field between specified source image and image instance.
+    /**
+       \param is_backward : if false, match I2(X+U(X)) = I1(X), else match I2(X) = I1(X-U(X)).
+    **/
+    CImg<T>& displacement(const CImg<T>& source, const float smoothness=0.1f, const float precision=5.0f,
+                          const unsigned int nb_scales=0, const unsigned int iteration_max=10000,
+                          const bool is_backward=false) {
+      return get_displacement(source,smoothness,precision,nb_scales,iteration_max,is_backward).move_to(*this);
+    }
+
+    CImg<Tfloat> get_displacement(const CImg<T>& source,
+                                  const float smoothness=0.1f, const float precision=5.0f,
+                                  const unsigned int nb_scales=0, const unsigned int iteration_max=10000,
+                                  const bool is_backward=false) const {
+      if (is_empty() || !source) return +*this;
+      if (!is_sameXYZC(source))
+        throw CImgArgumentException(_cimg_instance
+                                    "displacement() : Instance and source image (%u,%u,%u,%u,%p) have different dimensions.",
+                                    cimg_instance,
+                                    source._width,source._height,source._depth,source._spectrum,source._data);
+      if (precision<0)
+        throw CImgArgumentException(_cimg_instance
+                                    "displacement() : Invalid specified precision %g "
+                                    "(should be >=0)",
+                                    cimg_instance,
+                                    precision);
+      const unsigned int _nb_scales = nb_scales>0?nb_scales:(unsigned int)(2*std::log((double)(cimg::max(_width,_height))));
+      const float _precision = (float)std::pow(10.0,-(double)precision);
+      float sm, sM = source.max_min(sm), tm, tM = max_min(tm);
+      const float sdelta = sm==sM?1:(sM - sm), tdelta = tm==tM?1:(tM - tm);
+      const bool is_3d = source._depth>1;
+      CImg<floatT> U;
+
+      for (int scale = _nb_scales-1; scale>=0; --scale) {
+        const float factor = (float)std::pow(1.5,(double)scale);
+        const unsigned int
+          _sw = (unsigned int)(_width/factor), sw = _sw?_sw:1,
+          _sh = (unsigned int)(_height/factor), sh = _sh?_sh:1,
+          _sd = (unsigned int)(_depth/factor), sd = _sd?_sd:1;
+        const CImg<Tfloat>
+          I1 = (source.get_resize(sw,sh,sd,-100,2)-=sm)/=sdelta,
+          I2 = (get_resize(I1,2)-=tm)/=tdelta;
+        if (U) (U*=1.5f).resize(I2._width,I2._height,I2._depth,-100,3);
+        else U.assign(I2._width,I2._height,I2._depth,is_3d?3:2,0);
+        float dt = 2, energy = cimg::type<float>::max();
+        const CImgList<Tfloat> dI = is_backward?I1.get_gradient():I2.get_gradient();
+
+        for (unsigned int iteration = 0; iteration<iteration_max; ++iteration) {
+          float _energy = 0;
+
+          if (is_3d) { // 3d version.
+            if (smoothness>=0) cimg_for3XYZ(U,x,y,z) { // Isotropic regularization.
+                const float
+                  X = is_backward?x - U(x,y,z,0):x + U(x,y,z,0),
+                  Y = is_backward?y - U(x,y,z,1):y + U(x,y,z,1),
+                  Z = is_backward?z - U(x,y,z,2):z + U(x,y,z,2);
+                float deltaI = 0, _energy_regul = 0;
+                if (is_backward) cimg_forC(I2,c) deltaI+=(float)(I1.linear_atXYZ(X,Y,Z,c) - I2(x,y,z,c));
+                else cimg_forC(I2,c) deltaI+=(float)(I1(x,y,z,c) - I2.linear_atXYZ(X,Y,Z,c));
+                cimg_forC(U,c) {
+                  const float
+                    Ux = 0.5f*(U(_n1x,y,z,c) - U(_p1x,y,z,c)),
+                    Uy = 0.5f*(U(x,_n1y,z,c) - U(x,_p1y,z,c)),
+                    Uz = 0.5f*(U(x,y,_n1z,c) - U(x,y,_p1z,c)),
+                    Uxx = U(_n1x,y,z,c) + U(_p1x,y,z,c),
+                    Uyy = U(x,_n1y,z,c) + U(x,_p1y,z,c),
+                    Uzz = U(x,y,_n1z,c) + U(x,y,_p1z,c);
+                  U(x,y,z,c) = (float)(U(x,y,z,c) + dt*(deltaI*dI[c].linear_atXYZ(X,Y,Z) + smoothness* ( Uxx + Uyy + Uzz)))/(1+6*smoothness*dt);
+                  _energy_regul+=Ux*Ux + Uy*Uy + Uz*Uz;
+                }
+                _energy+=deltaI*deltaI + smoothness*_energy_regul;
+              } else {
+              const float nsmoothness = -smoothness;
+              cimg_for3XYZ(U,x,y,z) { // Anisotropic regularization.
+                const float
+                  X = is_backward?x - U(x,y,z,0):x + U(x,y,z,0),
+                  Y = is_backward?y - U(x,y,z,1):y + U(x,y,z,1),
+                  Z = is_backward?z - U(x,y,z,2):z + U(x,y,z,2);
+                float deltaI = 0, _energy_regul = 0;
+                if (is_backward) cimg_forC(I2,c) deltaI+=(float)(I1.linear_atXYZ(X,Y,Z,c) - I2(x,y,z,c));
+                else cimg_forC(I2,c) deltaI+=(float)(I1(x,y,z,c) - I2.linear_atXYZ(X,Y,Z,c));
+                cimg_forC(U,c) {
+                  const float
+                    Ux = 0.5f*(U(_n1x,y,z,c) - U(_p1x,y,z,c)),
+                    Uy = 0.5f*(U(x,_n1y,z,c) - U(x,_p1y,z,c)),
+                    Uz = 0.5f*(U(x,y,_n1z,c) - U(x,y,_p1z,c)),
+                    N2 = Ux*Ux + Uy*Uy + Uz*Uz,
+                    N = std::sqrt(N2),
+                    N3 = 1e-5 + N2*N,
+                    coef_a = (1 - Ux*Ux/N2)/N,
+                    coef_b = -Ux*Uy/N3,
+                    coef_c = -Ux*Uz/N3,
+                    coef_d = (1 - Uy*Uy/N2)/N,
+                    coef_e = -Uy*Uz/N3,
+                    coef_f = (1 - Uz*Uz/N2)/N,
+                    Uxx = U(_n1x,y,z,c) + U(_p1x,y,z,c),
+                    Uyy = U(x,_n1y,z,c) + U(x,_p1y,z,c),
+                    Uzz = U(x,y,_n1z,c) + U(x,y,_p1z,c),
+                    Uxy = 0.25f*(U(_n1x,_n1y,z,c) + U(_p1x,_p1y,z,c) - U(_n1x,_p1y,z,c) - U(_n1x,_p1y,z,c)),
+                    Uxz = 0.25f*(U(_n1x,y,_n1z,c) + U(_p1x,y,_p1z,c) - U(_n1x,y,_p1z,c) - U(_n1x,y,_p1z,c)),
+                    Uyz = 0.25f*(U(x,_n1y,_n1z,c) + U(x,_p1y,_p1z,c) - U(x,_n1y,_p1z,c) - U(x,_n1y,_p1z,c));
+                  U(x,y,z,c) = (float)(U(x,y,z,c) + dt*(deltaI*dI[c].linear_atXYZ(X,Y,Z) +
+                                                        nsmoothness* ( coef_a*Uxx + 2*coef_b*Uxy + 2*coef_c*Uxz + coef_d*Uyy + 2*coef_e*Uyz + coef_f*Uzz ))
+                                       )/(1+2*(coef_a+coef_d+coef_f)*nsmoothness*dt);
+                  _energy_regul+=N;
+                }
+                _energy+=deltaI*deltaI + nsmoothness*_energy_regul;
+              }
+            }
+          } else { // 2d version.
+            if (smoothness>=0) cimg_for3XY(U,x,y) { // Isotropic regularization.
+                const float
+                  X = is_backward?x - U(x,y,0):x + U(x,y,0),
+                  Y = is_backward?y - U(x,y,1):y + U(x,y,1);
+                float deltaI = 0, _energy_regul = 0;
+                if (is_backward) cimg_forC(I2,c) deltaI+=(float)(I1.linear_atXY(X,Y,c) - I2(x,y,c));
+                else cimg_forC(I2,c) deltaI+=(float)(I1(x,y,c) - I2.linear_atXY(X,Y,c));
+                cimg_forC(U,c) {
+                  const float
+                    Ux = 0.5f*(U(_n1x,y,c) - U(_p1x,y,c)),
+                    Uy = 0.5f*(U(x,_n1y,c) - U(x,_p1y,c)),
+                    Uxx = U(_n1x,y,c) + U(_p1x,y,c),
+                    Uyy = U(x,_n1y,c) + U(x,_p1y,c);
+                  U(x,y,c) = (float)(U(x,y,c) + dt*(deltaI*dI[c].linear_atXY(X,Y) + smoothness*( Uxx + Uyy )))/(1+4*smoothness*dt);
+                  _energy_regul+=Ux*Ux + Uy*Uy;
+                }
+                _energy+=deltaI*deltaI + smoothness*_energy_regul;
+              } else {
+              const float nsmoothness = -smoothness;
+              cimg_for3XY(U,x,y) { // Anisotropic regularization.
+                const float
+                  X = is_backward?x - U(x,y,0):x + U(x,y,0),
+                  Y = is_backward?y - U(x,y,1):y + U(x,y,1);
+                float deltaI = 0, _energy_regul = 0;
+                if (is_backward) cimg_forC(I2,c) deltaI+=(float)(I1.linear_atXY(X,Y,c) - I2(x,y,c));
+                else cimg_forC(I2,c) deltaI+=(float)(I1(x,y,c) - I2.linear_atXY(X,Y,c));
+                cimg_forC(U,c) {
+                  const float
+                    Ux = 0.5f*(U(_n1x,y,c) - U(_p1x,y,c)),
+                    Uy = 0.5f*(U(x,_n1y,c) - U(x,_p1y,c)),
+                    N2 = Ux*Ux + Uy*Uy,
+                    N = std::sqrt(N2),
+                    N3 = 1e-5 + N2*N,
+                    coef_a = Uy*Uy/N3,
+                    coef_b = -Ux*Uy/N3,
+                    coef_c = Ux*Ux/N3,
+                    Uxx = U(_n1x,y,c) + U(_p1x,y,c),
+                    Uyy = U(x,_n1y,c) + U(x,_p1y,c),
+                    Uxy = 0.25f*(U(_n1x,_n1y,c) + U(_p1x,_p1y,c) - U(_n1x,_p1y,c) - U(_n1x,_p1y,c));
+                  U(x,y,c) = (float)(U(x,y,c) + dt*(deltaI*dI[c].linear_atXY(X,Y) + nsmoothness*( coef_a*Uxx + 2*coef_b*Uxy + coef_c*Uyy )))/(1+2*(coef_a+coef_c)*nsmoothness*dt);
+                  _energy_regul+=N;
+                }
+                _energy+=deltaI*deltaI + nsmoothness*_energy_regul;
+              }
+            }
+          }
+          const float d_energy = (_energy - energy)/(sw*sh*sd);
+          if (d_energy<=0 && -d_energy<_precision) break;
+          if (d_energy>0) dt*=0.5f;
+          energy = _energy;
+        }
+      }
+      return U;
+    }
+
+    //! Compute the distance transform according to a specified value.
+    /** The distance transform implementation has been submitted by A. Meijster, and implements
+        the article 'W.H. Hesselink, A. Meijster, J.B.T.M. Roerdink,
+                     "A general algorithm for computing distance transforms in linear time.",
+                     In: Mathematical Morphology and its Applications to Image and Signal Processing,
+                     J. Goutsias, L. Vincent, and D.S. Bloomberg (eds.), Kluwer, 2000, pp. 331-340.'
+         The submitted code has then been modified to fit CImg coding style and constraints.
+    **/
+    CImg<T>& distance(const T value, const unsigned int metric=2) {
+      if (is_empty()) return *this;
+      bool is_value = false;
+      cimg_for(*this,ptr,T) *ptr = (T)(*ptr==value?is_value=true,0:999999999);
+      if (!is_value) return fill(cimg::type<T>::max());
+      switch (metric) {
+      case 0 : return _distance_core(_distance_sep_cdt,_distance_dist_cdt);          // Chebyshev.
+      case 1 : return _distance_core(_distance_sep_mdt,_distance_dist_mdt);          // Manhattan.
+      case 3 : return _distance_core(_distance_sep_edt,_distance_dist_edt);          // Euclidean.
+      default : return _distance_core(_distance_sep_edt,_distance_dist_edt).sqrt();  // Squared Euclidean.
+      }
+      return *this;
+    }
+
+    CImg<Tfloat> get_distance(const T value, const unsigned int metric=2) const {
+      return CImg<Tfloat>(*this,false).distance((Tfloat)value,metric);
+    }
+
+    static long _distance_sep_edt(const long i, const long u, const long *const g) {
+      return (u*u-i*i+g[u]-g[i])/(2*(u-i));
+    }
+
+    static long _distance_dist_edt(const long x, const long i, const long *const g) {
+      return (x-i)*(x-i) + g[i];
+    }
+
+    static long _distance_sep_mdt(const long i, const long u, const long *const g) {
+      return (u-i<=g[u]-g[i]?999999999:(g[u]-g[i]+u+i)/2);
+    }
+
+    static long _distance_dist_mdt(const long x, const long i, const long *const g) {
+      return (x<i?i-x:x-i) + g[i];
+    }
+
+    static long _distance_sep_cdt(const long i, const long u, const long *const g) {
+      const long h = (i+u)/2;
+      if (g[i]<=g[u]) { return h<i+g[u]?i+g[u]:h; }
+      return h<u-g[i]?h:u-g[i];
+    }
+
+    static long _distance_dist_cdt(const long x, const long i, const long *const g) {
+      const long d = x<i?i-x:x-i;
+      return d<g[i]?g[i]:d;
+    }
+
+    static void _distance_scan(const unsigned int len,
+                               const long *const g,
+                               long (*const sep)(const long, const long, const long *const),
+                               long (*const f)(const long, const long, const long *const),
+                               long *const s,
+                               long *const t,
+                               long *const dt) {
+      long q = s[0] = t[0] = 0;
+      for (int u = 1; u<(int)len; ++u) { // Forward scan.
+        while ((q>=0) && f(t[q],s[q],g)>f(t[q],u,g)) { --q; }
+        if (q<0) { q = 0; s[0] = u; }
+        else { const long w = 1 + sep(s[q], u, g); if (w<(long)len) { ++q; s[q] = u; t[q] = w; }}
+      }
+      for (int u = (int)len-1; u>=0; --u) { dt[u] = f(u,s[q],g); if (u==t[q]) --q; } // Backward scan.
+    }
+
+    CImg<T>& _distance_core(long (*const sep)(const long, const long, const long *const),
+                            long (*const f)(const long, const long, const long *const)) {
+      const unsigned int wh = _width*_height;
+      cimg_forC(*this,c) {
+        CImg<longT> g(_width), dt(_width), s(_width), t(_width);
+        CImg<T> img = get_shared_channel(c);
+        cimg_forYZ(*this,y,z) { // Over X-direction.
+          cimg_forX(*this,x) g[x] = (long)img(x,y,z,0,wh);
+          _distance_scan(_width,g,sep,f,s,t,dt);
+          cimg_forX(*this,x) img(x,y,z,0,wh) = (T)dt[x];
+        }
+        g.assign(_height); dt.assign(_height); s.assign(_height); t.assign(_height);
+        cimg_forXZ(*this,x,z) { // Over Y-direction.
+          cimg_forY(*this,y) g[y] = (long)img(x,y,z,0,wh);
+          _distance_scan(_height,g,sep,f,s,t,dt);
+          cimg_forY(*this,y) img(x,y,z,0,wh) = (T)dt[y];
+        }
+        if (_depth>1) {
+          g.assign(_depth); dt.assign(_depth); s.assign(_depth); t.assign(_depth);
+          cimg_forXY(*this,x,y) { // Over Z-direction.
+            cimg_forZ(*this,z) g[z] = (long)img(x,y,z,0,wh);
+            _distance_scan(_depth,g,sep,f,s,t,dt);
+            cimg_forZ(*this,z) img(x,y,z,0,wh) = (T)dt[z];
+          }
+        }
+      }
+      return *this;
+    }
+
+    //! Compute the chamfer distance transform according to a specified value, with a custom metric.
+    /**
+       The algorithm code has been initially proposed by A. Meijster, and modified by D. Tschumperlé.
+     **/
+    template<typename t>
+    CImg<T>& distance(const T value, const CImg<t>& metric_mask) {
+      if (is_empty()) return *this;
+      bool is_value = false;
+      cimg_for(*this,ptr,T) *ptr = (T)(*ptr==value?is_value=true,0:999999999);
+      if (!is_value) return fill(cimg::type<T>::max());
+      const unsigned long wh = _width*_height;
+      cimg_forC(*this,c) {
+        CImg<T> img = get_shared_channel(c);
+        cimg_forXYZ(metric_mask,dx,dy,dz) {
+          const t weight = metric_mask(dx,dy,dz);
+          if (weight) {
+            for (int z = dz, nz = 0; z<depth(); ++z,++nz) { // Forward scan.
+              for (int y = dy , ny = 0; y<height(); ++y,++ny) {
+                for (int x = dx, nx = 0; x<width(); ++x,++nx) {
+                  const T dd = img(nx,ny,nz,0,wh) + weight;
+                  if (dd<img(x,y,z,0,wh)) img(x,y,z,0,wh) = dd;
+                }
+              }
+            }
+            for (int z = depth() - 1 - dz, nz = depth() - 1; z>=0; --z,--nz) { // Backward scan.
+              for (int y = height() - 1 - dy, ny = height() - 1; y>=0; --y,--ny) {
+                for (int x = width() - 1 - dx, nx = width() - 1; x>=0; --x,--nx) {
+                  const T dd = img(nx,ny,nz,0,wh) + weight;
+                  if (dd<img(x,y,z,0,wh)) img(x,y,z,0,wh) = dd;
+                }
+              }
+            }
+          }
+        }
+      }
+      return *this;
+    }
+
+    template<typename t>
+    CImg<Tfloat> get_distance(const T value, const CImg<t>& metric_mask) const {
+      return CImg<Tfloat>(*this,false).distance(value,metric_mask);
+    }
+
+    //! Compute the distance map to one specified point.
+    CImg<T>& distance_dijkstra(const unsigned int x=0, const unsigned int y=0, const unsigned int z=0) {
+      return get_distance_dijkstra(x,y,z).move_to(*this);
+    }
+
+    //! Compute the distance map to one specified point \newinstance.
+    CImg<Tfloat> get_distance_dijkstra(const unsigned int x=0, const unsigned int y=0, const unsigned int z=0) const {
+      if (is_empty()) return *this;
+      if (!containsXYZC(x,y,z,0))
+        throw CImgArgumentException(_cimg_instance
+                                    "distance_dijkstra() : image instance does not contain specified starting point (%u,%u,%u).",
+                                    cimg_instance,
+                                    x,y,z);
+      if (_spectrum!=1)
+        throw CImgInstanceException(_cimg_instance
+                                    "distance_dijkstra() : image instance is not a scalar image.",
+                                    cimg_instance);
+      CImg<Tfloat> res(_width,_height,_depth,2);
+      CImg<boolT> in_queue(_width,_height,_depth,1,0);
+      CImg<Tint> Q;
+      unsigned int sizeQ = 0;
+
+      // Put specified point in priority queue.
+      Q._priority_queue_insert(in_queue,sizeQ,0,x,y,z);
+      res(x,y,z) = 0; res(x,y,z,1) = 0;
+
+      // Start distance propagation.
+      while (sizeQ) {
+
+        // Get and remove point with minimal potential from the queue.
+        const int x = (int)Q(0,1), y = (int)Q(0,2), z = (int)Q(0,3);
+        const Tfloat potential = (Tfloat)-Q(0,0);
+        Q._priority_queue_remove(sizeQ);
+
+        // Update neighbors.
+        Tfloat npot = 0;
+        if (x-1>=0 && Q._priority_queue_insert(in_queue,sizeQ,-(npot=(*this)(x-1,y,z)+potential),x-1,y,z)) {
+          res(x-1,y,z) = npot; res(x-1,y,z,1) = 2;
+        }
+        if (x+1<width() && Q._priority_queue_insert(in_queue,sizeQ,-(npot=(*this)(x+1,y,z)+potential),x+1,y,z)) {
+          res(x+1,y,z) = npot; res(x+1,y,z,1) = 1;
+        }
+        if (y-1>=0 && Q._priority_queue_insert(in_queue,sizeQ,-(npot=(*this)(x,y-1,z)+potential),x,y-1,z)) {
+          res(x,y-1,z) = npot; res(x,y-1,z,1) = 4;
+        }
+        if (y+1<height() && Q._priority_queue_insert(in_queue,sizeQ,-(npot=(*this)(x,y+1,z)+potential),x,y+1,z)) {
+          res(x,y+1,z) = npot; res(x,y+1,z,1) = 3;
+        }
+        if (z-1>=0 && Q._priority_queue_insert(in_queue,sizeQ,-(npot=(*this)(x,y,z-1)+potential),x,y,z-1)) {
+          res(x,y,z-1) = npot; res(x,y,z+1,1) = 6;
+        }
+        if (z+1<depth() && Q._priority_queue_insert(in_queue,sizeQ,-(npot=(*this)(x,y,z+1)+potential),x,y,z+1)) {
+          res(x,y,z+1) = npot; res(x,y,z+1,1) = 5;
+        }
+      }
+      return res;
+    }
+
+    //! Compute distance function from 0-valued isophotes by the application of an Eikonal PDE.
+    CImg<T>& distance_eikonal(const unsigned int nb_iterations, const float band_size=0, const float time_step=0.5f) {
+      if (is_empty()) return *this;
+      CImg<Tfloat> velocity(*this);
+      for (unsigned int iteration = 0; iteration<nb_iterations; ++iteration) {
+        Tfloat *ptrd = velocity._data, veloc_max = 0;
+        if (_depth>1) { // 3d
+          CImg_3x3x3(I,Tfloat);
+          cimg_forC(*this,c) cimg_for3x3x3(*this,x,y,z,c,I,Tfloat) if (band_size<=0 || cimg::abs(Iccc)<band_size) {
+            const Tfloat
+              gx = (Incc - Ipcc)/2,
+              gy = (Icnc - Icpc)/2,
+              gz = (Iccn - Iccp)/2,
+              sgn = -cimg::sign(Iccc),
+              ix = gx*sgn>0?(Incc - Iccc):(Iccc - Ipcc),
+              iy = gy*sgn>0?(Icnc - Iccc):(Iccc - Icpc),
+              iz = gz*sgn>0?(Iccn - Iccc):(Iccc - Iccp),
+              ng = (Tfloat)(1e-5f + std::sqrt(gx*gx + gy*gy + gz*gz)),
+              ngx = gx/ng,
+              ngy = gy/ng,
+              ngz = gz/ng,
+              veloc = sgn*(ngx*ix + ngy*iy + ngz*iz - 1);
+            *(ptrd++) = veloc;
+            if (veloc>veloc_max) veloc_max = veloc; else if (-veloc>veloc_max) veloc_max = -veloc;
+          } else *(ptrd++) = 0;
+        } else { // 2d version
+          CImg_3x3(I,Tfloat);
+          cimg_forC(*this,c) cimg_for3x3(*this,x,y,0,c,I,Tfloat) if (band_size<=0 || cimg::abs(Icc)<band_size) {
+            const Tfloat
+              gx = (Inc - Ipc)/2,
+              gy = (Icn - Icp)/2,
+              sgn = -cimg::sign(Icc),
+              ix = gx*sgn>0?(Inc - Icc):(Icc - Ipc),
+              iy = gy*sgn>0?(Icn - Icc):(Icc - Icp),
+              ng = (Tfloat)(1e-5f + std::sqrt(gx*gx + gy*gy)),
+              ngx = gx/ng,
+              ngy = gy/ng,
+              veloc = sgn*(ngx*ix + ngy*iy - 1);
+            *(ptrd++) = veloc;
+            if (veloc>veloc_max) veloc_max = veloc; else if (-veloc>veloc_max) veloc_max = -veloc;
+          } else *(ptrd++) = 0;
+        }
+        if (veloc_max>0) *this+=(velocity*=time_step/veloc_max);
+      }
+      return *this;
+    }
+
+    CImg<Tfloat> get_distance_eikonal(const unsigned int nb_iterations, const float band_size=0, const float time_step=0.5f) const {
+      return CImg<Tfloat>(*this,false).distance_eikonal(nb_iterations,band_size,time_step);
+    }
+
+    //! 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).move_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_instance
+                                          "haar() : Sub-image width %u is not even at scale %u.",
+                                          cimg_instance,
+                                          w);
+
+            res.assign(_width,_height,_depth,_spectrum);
+            if (invert) cimg_forYZC(*this,y,z,c) { // 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,c), val1 = (Tfloat)(*this)(xw,y,z,c);
+                res(x2++,y,z,c) = val0 - val1;
+                res(x2++,y,z,c) = val0 + val1;
+              }
+            } else cimg_forYZC(*this,y,z,c) { // 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,c), val1 = (Tfloat)(*this)(x2++,y,z,c);
+                res(x,y,z,c) = (val0 + val1)/2;
+                res(xw,y,z,c) = (val1 - val0)/2;
+              }
+            }
+          } else return *this;
+        } break;
+        case 'y' : {
+          const unsigned int h = _height/2;
+          if (h) {
+            if (h%2)
+              throw CImgInstanceException(_cimg_instance
+                                          "haar() : Sub-image height %u is not even at scale %u.",
+                                          cimg_instance,
+                                          h);
+
+            res.assign(_width,_height,_depth,_spectrum);
+            if (invert) cimg_forXZC(*this,x,z,c) { // 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,c), val1 = (Tfloat)(*this)(x,yh,z,c);
+                res(x,y2++,z,c) = val0 - val1;
+                res(x,y2++,z,c) = val0 + val1;
+              }
+            } else cimg_forXZC(*this,x,z,c) {
+              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,c), val1 = (Tfloat)(*this)(x,y2++,z,c);
+                res(x,y,z,c) = (val0 + val1)/2;
+                res(x,yh,z,c) = (val1 - val0)/2;
+              }
+            }
+          } else return *this;
+        } break;
+        case 'z' : {
+          const unsigned int d = _depth/2;
+          if (d) {
+            if (d%2)
+              throw CImgInstanceException(_cimg_instance
+                                          "haar() : Sub-image depth %u is not even at scale %u.",
+                                          cimg_instance,
+                                          d);
+
+            res.assign(_width,_height,_depth,_spectrum);
+            if (invert) cimg_forXYC(*this,x,y,c) { // 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,c), val1 = (Tfloat)(*this)(x,y,zd,c);
+                res(x,y,z2++,c) = val0 - val1;
+                res(x,y,z2++,c) = val0 + val1;
+              }
+            } else cimg_forXYC(*this,x,y,c) {
+              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++,c), val1 = (Tfloat)(*this)(x,y,z2++,c);
+                res(x,y,z,c) = (val0 + val1)/2;
+                res(x,y,zd,c) = (val1 - val0)/2;
+              }
+            }
+          } else return *this;
+        } break;
+        default :
+          throw CImgArgumentException(_cimg_instance
+                                      "haar() : Invalid specified axis '%c' "
+                                      "(should be { x | y | z }).",
+                                      cimg_instance,
+                                      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_instance
+                                        "haar() : Invalid specified axis '%c' "
+                                        "(should be { x | y | z }).",
+                                        cimg_instance,
+                                        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_instance
+                                        "haar() : Invalid specified axis '%c' "
+                                        "(should be { x | y | z }).",
+                                        cimg_instance,
+                                        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).move_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).move_to(res);
+        if (_height>1) { if (res) res.haar('y',invert,1); else get_haar('y',invert,1).move_to(res); }
+        if (_depth>1) { if (res) res.haar('z',invert,1); else get_haar('z',invert,1).move_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;
+    }
+
+    //! Compute a 1d Fast Fourier Transform, along a specified axis.
+    CImgList<Tfloat> get_FFT(const char axis, const bool invert=false) const {
+      CImgList<Tfloat> res(*this,CImg<Tfloat>());
+      CImg<Tfloat>::FFT(res[0],res[1],axis,invert);
+      return res;
+    }
+
+    //! Compute a n-d Fast-Fourier Transform.
+    CImgList<Tfloat> get_FFT(const bool invert=false) const {
+      CImgList<Tfloat> res(*this,CImg<Tfloat>());
+      CImg<Tfloat>::FFT(res[0],res[1],invert);
+      return res;
+    }
+
+    //! Compute a 1d Fast Fourier Transform, along a specified axis.
+    static void FFT(CImg<T>& real, CImg<T>& imag, const char axis, const bool invert=false) {
+      if (!real)
+        throw CImgInstanceException("CImg<%s>::FFT() : Specified real part is empty.",
+                                    pixel_type());
+
+      if (!imag) imag.assign(real._width,real._height,real._depth,real._spectrum,0);
+      if (!real.is_sameXYZC(imag))
+        throw CImgInstanceException("CImg<%s>::FFT() : Specified real part (%u,%u,%u,%u,%p) and imaginary part (%u,%u,%u,%u,%p) have different dimensions.",
+                                    pixel_type(),
+                                    real._width,real._height,real._depth,real._spectrum,real._data,
+                                    imag._width,imag._height,imag._depth,imag._spectrum,imag._data);
+#ifdef cimg_use_fftw3
+      fftw_complex *data_in;
+      fftw_plan data_plan;
+
+      switch (cimg::uncase(axis)) {
+      case 'x' : { // Fourier along X, using FFTW library.
+        data_in = (fftw_complex*)fftw_malloc(sizeof(fftw_complex)*real._width);
+        if (!data_in) throw CImgInstanceException("CImgList<%s>::FFT() : Failed to allocate memory (%s) for computing FFT of image (%u,%u,%u,%u) along the X-axis.",
+                                                  pixel_type(),
+                                                  cimg::strbuffersize(real._width*sizeof(fftw_complex)),
+                                                  real._width,real._height,real._depth,real._spectrum);
+
+        data_plan = fftw_plan_dft_1d(real._width,data_in,data_in,invert?FFTW_BACKWARD:FFTW_FORWARD,FFTW_ESTIMATE);
+        cimg_forYZC(real,y,z,c) {
+          T *ptrr = real.data(0,y,z,c), *ptri = imag.data(0,y,z,c);
+          double *ptrd = (double*)data_in;
+          cimg_forX(real,x) { *(ptrd++) = (double)*(ptrr++); *(ptrd++) = (double)*(ptri++); }
+          fftw_execute(data_plan);
+          const unsigned int fact = real._width;
+          if (invert) cimg_forX(real,x) { *(--ptri) = (T)(*(--ptrd)/fact); *(--ptrr) = (T)(*(--ptrd)/fact); }
+          else cimg_forX(real,x) { *(--ptri) = (T)*(--ptrd); *(--ptrr) = (T)*(--ptrd); }
+        }
+      } break;
+      case 'y' : { // Fourier along Y, using FFTW library.
+        data_in = (fftw_complex*)fftw_malloc(sizeof(fftw_complex) * real._height);
+        if (!data_in) throw CImgInstanceException("CImgList<%s>::FFT() : Failed to allocate memory (%s) for computing FFT of image (%u,%u,%u,%u) along the Y-axis.",
+                                                  pixel_type(),
+                                                  cimg::strbuffersize(real._height*sizeof(fftw_complex)),
+                                                  real._width,real._height,real._depth,real._spectrum);
+
+        data_plan = fftw_plan_dft_1d(real._height,data_in,data_in,invert?FFTW_BACKWARD:FFTW_FORWARD,FFTW_ESTIMATE);
+        const unsigned int off = real._width;
+        cimg_forXZC(real,x,z,c) {
+          T *ptrr = real.data(x,0,z,c), *ptri = imag.data(x,0,z,c);
+          double *ptrd = (double*)data_in;
+          cimg_forY(real,y) { *(ptrd++) = (double)*ptrr; *(ptrd++) = (double)*ptri; ptrr+=off; ptri+=off; }
+          fftw_execute(data_plan);
+          const unsigned int fact = real._height;
+          if (invert) cimg_forY(real,y) { ptrr-=off; ptri-=off; *ptri = (T)(*(--ptrd)/fact); *ptrr = (T)(*(--ptrd)/fact); }
+          else cimg_forY(real,y) { ptrr-=off; ptri-=off; *ptri = (T)*(--ptrd); *ptrr = (T)*(--ptrd); }
+        }
+      } break;
+      case 'z' : { // Fourier along Z, using FFTW library.
+        data_in = (fftw_complex*)fftw_malloc(sizeof(fftw_complex) * real._depth);
+        if (!data_in) throw CImgInstanceException("CImgList<%s>::FFT() : Failed to allocate memory (%s) for computing FFT of image (%u,%u,%u,%u) along the Z-axis.",
+                                                  pixel_type(),
+                                                  cimg::strbuffersize(real._depth*sizeof(fftw_complex)),
+                                                  real._width,real._height,real._depth,real._spectrum);
+
+        data_plan = fftw_plan_dft_1d(real._depth,data_in,data_in,invert?FFTW_BACKWARD:FFTW_FORWARD,FFTW_ESTIMATE);
+        const unsigned int off = real._width*real._height;
+        cimg_forXYC(real,x,y,c) {
+          T *ptrr = real.data(x,y,0,c), *ptri = imag.data(x,y,0,c);
+          double *ptrd = (double*)data_in;
+          cimg_forZ(real,z) { *(ptrd++) = (double)*ptrr; *(ptrd++) = (double)*ptri; ptrr+=off; ptri+=off; }
+          fftw_execute(data_plan);
+          const unsigned int fact = real._depth;
+          if (invert) cimg_forZ(real,z) { ptrr-=off; ptri-=off; *ptri = (T)(*(--ptrd)/fact); *ptrr = (T)(*(--ptrd)/fact); }
+          else cimg_forZ(real,z) { ptrr-=off; ptri-=off; *ptri = (T)*(--ptrd); *ptrr = (T)*(--ptrd); }
+        }
+      } break;
+      default : { // Fourier along C, using FFTW library.
+        data_in = (fftw_complex*)fftw_malloc(sizeof(fftw_complex) * real._spectrum);
+        if (!data_in) throw CImgInstanceException("CImgList<%s>::FFT() : Failed to allocate memory (%s) for computing FFT of image (%u,%u,%u,%u) along the C-axis.",
+                                                  pixel_type(),
+                                                  cimg::strbuffersize(real._spectrum*sizeof(fftw_complex)),
+                                                  real._width,real._height,real._depth,real._spectrum);
+
+        data_plan = fftw_plan_dft_1d(real._spectrum,data_in,data_in,invert?FFTW_BACKWARD:FFTW_FORWARD,FFTW_ESTIMATE);
+        const unsigned int off = real._width*real._height*real._depth;
+        cimg_forXYZ(real,x,y,z) {
+          T *ptrr = real.data(x,y,z,0), *ptri = imag.data(x,y,z,0);
+          double *ptrd = (double*)data_in;
+          cimg_forC(real,c) { *(ptrd++) = (double)*ptrr; *(ptrd++) = (double)*ptri; ptrr+=off; ptri+=off; }
+          fftw_execute(data_plan);
+          const unsigned int fact = real._spectrum;
+          if (invert) cimg_forC(real,c) { ptrr-=off; ptri-=off; *ptri = (T)(*(--ptrd)/fact); *ptrr = (T)(*(--ptrd)/fact); }
+          else cimg_forC(real,c) { ptrr-=off; ptri-=off; *ptri = (T)*(--ptrd); *ptrr = (T)*(--ptrd); }
+        }
+      }
+      }
+      fftw_destroy_plan(data_plan);
+      fftw_free(data_in);
+#else
+      switch (cimg::uncase(axis)) {
+      case 'x' : { // Fourier along X, using built-in functions.
+        const unsigned int N = real._width, N2 = (N>>1);
+        if (((N-1)&N) && N!=1)
+          throw CImgInstanceException("CImgList<%s>::FFT() : Specified real and imaginary parts (%u,%u,%u,%u) have non 2^N dimension along the X-axis.",
+                                      pixel_type(),
+                                      real._width,real._height,real._depth,real._spectrum);
+
+        for (unsigned int i = 0, j = 0; i<N2; ++i) {
+          if (j>i) cimg_forYZC(real,y,z,c) {
+            cimg::swap(real(i,y,z,c),real(j,y,z,c)); cimg::swap(imag(i,y,z,c),imag(j,y,z,c));
+            if (j<N2) {
+              const unsigned int ri = N-1-i, rj = N-1-j;
+              cimg::swap(real(ri,y,z,c),real(rj,y,z,c)); cimg::swap(imag(ri,y,z,c),imag(rj,y,z,c));
+            }
+          }
+          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::PI/delta),
+                        ca = (float)std::cos(angle),
+                        sa = (float)std::sin(angle);
+            for (unsigned int k = 0; k<delta2; ++k) {
+              const unsigned int j = i + k, nj = j + delta2;
+              cimg_forYZC(real,y,z,c) {
+                T &ir = real(j,y,z,c), &ii = imag(j,y,z,c), &nir = real(nj,y,z,c), &nii = imag(nj,y,z,c);
+                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) { real/=N; imag/=N; }
+      } break;
+      case 'y' : { // Fourier along Y, using built-in functions.
+        const unsigned int N = real._height, N2 = (N>>1);
+        if (((N-1)&N) && N!=1)
+          throw CImgInstanceException("CImgList<%s>::FFT() : Specified real and imaginary parts (%u,%u,%u,%u) have non 2^N dimension along the Y-axis.",
+                                      pixel_type(),
+                                      real._width,real._height,real._depth,real._spectrum);
+
+        for (unsigned int i = 0, j = 0; i<N2; ++i) {
+          if (j>i) cimg_forXZC(real,x,z,c) {
+            cimg::swap(real(x,i,z,c),real(x,j,z,c)); cimg::swap(imag(x,i,z,c),imag(x,j,z,c));
+            if (j<N2) {
+              const unsigned int ri = N - 1 - i, rj = N - 1 - j;
+              cimg::swap(real(x,ri,z,c),real(x,rj,z,c)); cimg::swap(imag(x,ri,z,c),imag(x,rj,z,c));
+            }
+          }
+          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::PI/delta),
+                        ca = (float)std::cos(angle), sa = (float)std::sin(angle);
+            for (unsigned int k = 0; k<delta2; ++k) {
+              const unsigned int j = i + k, nj = j + delta2;
+              cimg_forXZC(real,x,z,c) {
+                T &ir = real(x,j,z,c), &ii = imag(x,j,z,c), &nir = real(x,nj,z,c), &nii = imag(x,nj,z,c);
+                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) { real/=N; imag/=N; }
+      } break;
+      case 'z' : { // Fourier along Z, using built-in functions.
+        const unsigned int N = real._depth, N2 = (N>>1);
+        if (((N-1)&N) && N!=1)
+          throw CImgInstanceException("CImgList<%s>::FFT() : Specified real and imaginary parts (%u,%u,%u,%u) have non 2^N dimension along the Z-axis.",
+                                      pixel_type(),
+                                      real._width,real._height,real._depth,real._spectrum);
+
+        for (unsigned int i = 0, j = 0; i<N2; ++i) {
+          if (j>i) cimg_forXYC(real,x,y,c) {
+            cimg::swap(real(x,y,i,c),real(x,y,j,c)); cimg::swap(imag(x,y,i,c),imag(x,y,j,c));
+            if (j<N2) {
+              const unsigned int ri = N - 1 - i, rj = N - 1 - j;
+              cimg::swap(real(x,y,ri,c),real(x,y,rj,c)); cimg::swap(imag(x,y,ri,c),imag(x,y,rj,c));
+            }
+          }
+          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::PI/delta),
+                        ca = (float)std::cos(angle), sa = (float)std::sin(angle);
+            for (unsigned int k = 0; k<delta2; ++k) {
+              const unsigned int j = i + k, nj = j + delta2;
+              cimg_forXYC(real,x,y,c) {
+                T &ir = real(x,y,j,c), &ii = imag(x,y,j,c), &nir = real(x,y,nj,c), &nii = imag(x,y,nj,c);
+                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) { real/=N; imag/=N; }
+      } break;
+      default :
+        throw CImgArgumentException("CImgList<%s>::FFT() : Invalid specified axis '%c' for real and imaginary parts (%u,%u,%u,%u) "
+                                    "(should be { x | y | z }).",
+                                    pixel_type(),axis,
+                                    real._width,real._height,real._depth,real._spectrum);
+      }
+#endif
+    }
+
+    //! Compute a n-d Fast Fourier Transform.
+    static void FFT(CImg<T>& real, CImg<T>& imag, const bool invert=false) {
+      if (!real)
+        throw CImgInstanceException("CImgList<%s>::FFT() : Empty specified real part.",
+                                    pixel_type());
+
+      if (!imag) imag.assign(real._width,real._height,real._depth,real._spectrum,0);
+      if (!real.is_sameXYZC(imag))
+        throw CImgInstanceException("CImgList<%s>::FFT() : Specified real part (%u,%u,%u,%u,%p) and imaginary part (%u,%u,%u,%u,%p) have different dimensions.",
+                                    pixel_type(),
+                                    real._width,real._height,real._depth,real._spectrum,real._data,
+                                    imag._width,imag._height,imag._depth,imag._spectrum,imag._data);
+
+#ifdef cimg_use_fftw3
+      fftw_complex *data_in = (fftw_complex*)fftw_malloc(sizeof(fftw_complex)*real._width*real._height*real._depth);
+      if (!data_in) throw CImgInstanceException("CImgList<%s>::FFT() : Failed to allocate memory (%s) for computing FFT of image (%u,%u,%u,%u).",
+                                                pixel_type(),
+                                                cimg::strbuffersize(real._width*real._height*real._depth*real._spectrum*sizeof(fftw_complex)),
+                                                real._width,real._height,real._depth,real._spectrum);
+
+      fftw_plan data_plan;
+      const unsigned int w = real._width, wh = w*real._height, whd = wh*real._depth;
+      data_plan = fftw_plan_dft_3d(real._width,real._height,real._depth,data_in,data_in,invert?FFTW_BACKWARD:FFTW_FORWARD,FFTW_ESTIMATE);
+      cimg_forC(real,c) {
+        T *ptrr = real.data(0,0,0,c), *ptri = imag.data(0,0,0,c);
+        double *ptrd = (double*)data_in;
+        for (unsigned int x = 0; x<real._width; ++x, ptrr-=wh-1, ptri-=wh-1)
+          for (unsigned int y = 0; y<real._height; ++y, ptrr-=whd-w, ptri-=whd-w)
+            for (unsigned int z = 0; z<real._depth; ++z, ptrr+=wh, ptri+=wh) {
+              *(ptrd++) = (double)*ptrr; *(ptrd++) = (double)*ptri;
+            }
+        fftw_execute(data_plan);
+        ptrd = (double*)data_in;
+        ptrr = real.data(0,0,0,c);
+        ptri = imag.data(0,0,0,c);
+        if (!invert) for (unsigned int x = 0; x<real._width; ++x, ptrr-=wh-1, ptri-=wh-1)
+          for (unsigned int y = 0; y<real._height; ++y, ptrr-=whd-w, ptri-=whd-w)
+            for (unsigned int z = 0; z<real._depth; ++z, ptrr+=wh, ptri+=wh) {
+              *ptrr = (T)*(ptrd++); *ptri = (T)*(ptrd++);
+            }
+        else for (unsigned int x = 0; x<real._width; ++x, ptrr-=wh-1, ptri-=wh-1)
+          for (unsigned int y = 0; y<real._height; ++y, ptrr-=whd-w, ptri-=whd-w)
+            for (unsigned int z = 0; z<real._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 (real._depth>1)  FFT(real,imag,'z',invert);
+      if (real._height>1) FFT(real,imag,'y',invert);
+      if (real._width>1)  FFT(real,imag,'x',invert);
+#endif
+    }
+
+    //@}
+    //-------------------------------------
+    //
+    //! \name 3d Objects Management
+    //@{
+    //-------------------------------------
+
+    //! Shift a 3d object.
+    CImg<T>& shift_object3d(const float tx, const float ty=0, const float tz=0) {
+      if (_height!=3 || _depth>1 || _spectrum>1)
+        throw CImgInstanceException(_cimg_instance
+                                    "shift_object3d() : Instance is not a set of 3d vertices.",
+                                    cimg_instance);
+
+      get_shared_line(0)+=tx; get_shared_line(1)+=ty; get_shared_line(2)+=tz;
+      return *this;
+    }
+
+    CImg<Tfloat> get_shift_object3d(const float tx, const float ty=0, const float tz=0) const {
+      return CImg<Tfloat>(*this,false).shift_object3d(tx,ty,tz);
+    }
+
+    //! Shift a 3d object so that it becomes centered.
+    CImg<T>& shift_object3d() {
+      if (_height!=3 || _depth>1 || _spectrum>1)
+        throw CImgInstanceException(_cimg_instance
+                                    "shift_object3d() : Instance is not a set of 3d vertices.",
+                                    cimg_instance);
+
+      CImg<T> xcoords = get_shared_line(0), ycoords = get_shared_line(1), zcoords = get_shared_line(2);
+      float xm, xM = (float)xcoords.max_min(xm), ym, yM = (float)ycoords.max_min(ym), zm, zM = (float)zcoords.max_min(zm);
+      xcoords-=(xm + xM)/2; ycoords-=(ym + yM)/2; zcoords-=(zm + zM)/2;
+      return *this;
+    }
+
+    CImg<Tfloat> get_shift_object3d() const {
+      return CImg<Tfloat>(*this,false).shift_object3d();
+    }
+
+    //! Resize a 3d object.
+    CImg<T>& resize_object3d(const float sx, const float sy=-100, const float sz=-100) {
+      if (_height!=3 || _depth>1 || _spectrum>1)
+        throw CImgInstanceException(_cimg_instance
+                                    "resize_object3d() : Instance is not a set of 3d vertices.",
+                                    cimg_instance);
+
+      CImg<T> xcoords = get_shared_line(0), ycoords = get_shared_line(1), zcoords = get_shared_line(2);
+      float xm, xM = (float)xcoords.max_min(xm), ym, yM = (float)ycoords.max_min(ym), zm, zM = (float)zcoords.max_min(zm);
+      if (xm<xM) { if (sx>0) xcoords*=sx/(xM-xm); else xcoords*=-sx/100; }
+      if (ym<yM) { if (sy>0) ycoords*=sy/(yM-ym); else ycoords*=-sy/100; }
+      if (zm<zM) { if (sz>0) zcoords*=sz/(zM-zm); else zcoords*=-sz/100; }
+      return *this;
+    }
+
+    CImg<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() {
+      if (_height!=3 || _depth>1 || _spectrum>1)
+        throw CImgInstanceException(_cimg_instance
+                                    "resize_object3d() : Instance is not a set of 3d vertices.",
+                                    cimg_instance);
+
+      CImg<T> xcoords = get_shared_line(0), ycoords = get_shared_line(1), zcoords = get_shared_line(2);
+      float xm, xM = (float)xcoords.max_min(xm), ym, yM = (float)ycoords.max_min(ym), zm, zM = (float)zcoords.max_min(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_vertices, const CImgList<tff>& obj_primitives) {
+      if (!obj_vertices || !obj_primitives) return *this;
+      if (obj_vertices._height!=3 || obj_vertices._depth>1 || obj_vertices._spectrum>1)
+        throw CImgInstanceException(_cimg_instance
+                                    "append_object3d() : Specified vertice image (%u,%u,%u,%u,%p) is not a set of 3d vertices.",
+                                    cimg_instance,
+                                    obj_vertices._width,obj_vertices._height,obj_vertices._depth,obj_vertices._spectrum,obj_vertices._data);
+
+      if (is_empty()) { primitives.assign(obj_primitives); return assign(obj_vertices); }
+      if (_height!=3 || _depth>1 || _spectrum>1)
+        throw CImgInstanceException(_cimg_instance
+                                    "append_object3d() : Instance is not a set of 3d vertices.",
+                                    cimg_instance);
+
+      const unsigned int P = _width;
+      append(obj_vertices,'x');
+      const unsigned int N = primitives._width;
+      primitives.insert(obj_primitives);
+      for (unsigned int i = N; i<primitives._width; ++i) {
+        CImg<tf> &p = primitives[i];
+        switch (p.size()) {
+        case 1 : p[0]+=P; break; // Point.
+        case 5 : p[0]+=P; p[1]+=P; break; // Sphere.
+        case 2 : case 6 : p[0]+=P; p[1]+=P; break; // Segment.
+        case 3 : case 9 : p[0]+=P; p[1]+=P; p[2]+=P; break; // Triangle.
+        case 4 : case 12 : p[0]+=P; p[1]+=P; p[2]+=P; p[3]+=P; break; // Rectangle.
+        }
+      }
+      return *this;
+    }
+
+    //! Texturize primitives of a 3d object.
+    template<typename tp, typename tc, typename tt, typename tx>
+    const CImg<T>& texturize_object3d(CImgList<tp>& primitives, CImgList<tc>& colors,
+                                      const CImg<tt>& texture, const CImg<tx>& coords=CImg<tx>::empty()) const {
+      if (is_empty()) return *this;
+      if (_height!=3)
+        throw CImgInstanceException(_cimg_instance
+                                    "texturize_object3d() : image instance is not a set of 3d points.",
+                                    cimg_instance);
+      if (coords && (coords._width!=_width || coords._height!=2))
+        throw CImgArgumentException(_cimg_instance
+                                    "texturize_object3d() : Invalid specified texture coordinates (%u,%u,%u,%u,%p).",
+                                    cimg_instance,
+                                    coords._width,coords._height,coords._depth,coords._spectrum,coords._data);
+      CImg<unsigned int> _coords;
+      if (!coords) { // If no texture coordinates specified, do a default XY-projection.
+        _coords.assign(_width,2);
+        float
+          xmin, xmax = (float)get_shared_line(0).max_min(xmin),
+          ymin, ymax = (float)get_shared_line(1).max_min(ymin),
+          dx = xmax>xmin?xmax-xmin:1,
+          dy = ymax>ymin?ymax-ymin:1;
+        cimg_forX(*this,p) {
+          _coords(p,0) = (unsigned int)(((*this)(p,0)-xmin)*(texture._width-1)/dx);
+          _coords(p,1) = (unsigned int)(((*this)(p,1)-ymin)*(texture._height-1)/dy);
+        }
+      } else _coords = coords;
+
+      int texture_ind = -1;
+      cimglist_for(primitives,l) {
+        CImg<tp> &p = primitives[l];
+        const unsigned int siz = p.size();
+        switch (siz) {
+        case 1 : { // Point.
+          const unsigned int
+            i0 = (unsigned int)p[0],
+            x0 = (unsigned int)_coords(i0,0), y0 = (unsigned int)_coords(i0,1);
+          texture.get_vector_at(x0,y0).move_to(colors[l]);
+        } break;
+        case 2 : case 6 : { // Line.
+          const unsigned int
+            i0 = (unsigned int)p[0], i1 = (unsigned int)p[1],
+            x0 = (unsigned int)_coords(i0,0), y0 = (unsigned int)_coords(i0,1),
+            x1 = (unsigned int)_coords(i1,0), y1 = (unsigned int)_coords(i1,1);
+          if (texture_ind<0) colors[texture_ind=l] = texture; else colors[l].assign(colors[texture_ind],true);
+          CImg<tp>::vector(i0,i1,x0,y0,x1,y1).move_to(p);
+        } break;
+        case 3 : case 9 : { // Triangle.
+          const unsigned int
+            i0 = (unsigned int)p[0], i1 = (unsigned int)p[1], i2 = (unsigned int)p[2],
+            x0 = (unsigned int)_coords(i0,0), y0 = (unsigned int)_coords(i0,1),
+            x1 = (unsigned int)_coords(i1,0), y1 = (unsigned int)_coords(i1,1),
+            x2 = (unsigned int)_coords(i2,0), y2 = (unsigned int)_coords(i2,1);
+          if (texture_ind<0) colors[texture_ind=l] = texture; else colors[l].assign(colors[texture_ind],true);
+          CImg<tp>::vector(i0,i1,i2,x0,y0,x1,y1,x2,y2).move_to(p);
+        } break;
+        case 4 : case 12 : { // Quadrangle.
+          const unsigned int
+            i0 = (unsigned int)p[0], i1 = (unsigned int)p[1], i2 = (unsigned int)p[2], i3 = (unsigned int)p[3],
+            x0 = (unsigned int)_coords(i0,0), y0 = (unsigned int)_coords(i0,1),
+            x1 = (unsigned int)_coords(i1,0), y1 = (unsigned int)_coords(i1,1),
+            x2 = (unsigned int)_coords(i2,0), y2 = (unsigned int)_coords(i2,1),
+            x3 = (unsigned int)_coords(i3,0), y3 = (unsigned int)_coords(i3,1);
+          if (texture_ind<0) colors[texture_ind=l] = texture; else colors[l].assign(colors[texture_ind],true);
+          CImg<tp>::vector(i0,i1,i2,i3,x0,y0,x1,y1,x2,y2,x3,y3).move_to(p);
+        } break;
+        }
+      }
+      return *this;
+    }
+
+    //! Create and return a 3d elevation of the image instance.
+    /**
+       \param[out] primitives The returned list of the 3d object primitives
+                              (template type \e tf should be at least \e unsigned \e int).
+       \param[out] colors The returned list of the 3d object colors.
+       \param elevation The input elevation map.
+       \return The N vertices (xi,yi,zi) of the 3d object as a Nx3 CImg<float> image (0<=i<=N-1).
+       \par Example
+       \code
+       const CImg<float> img("reference.jpg");
+       CImgList<unsigned int> faces3d;
+       CImgList<unsigned char> colors3d;
+       const CImg<float> points3d = img.get_elevation3d(faces3d,colors,img.get_norm()*0.2);
+       CImg<unsigned char>().display_object3d("Elevation3d",points3d,faces3d,colors3d);
+       \endcode
+       \image html ref_elevation3d.jpg
+    **/
+    template<typename tf, typename tc, typename te>
+    CImg<floatT> get_elevation3d(CImgList<tf>& primitives, CImgList<tc>& colors, const CImg<te>& elevation) const {
+      if (!is_sameXY(elevation) || elevation._depth>1 || elevation._spectrum>1)
+        throw CImgArgumentException(_cimg_instance
+                                    "get_elevation3d() : Instance and specified elevation (%u,%u,%u,%u,%p) "
+                                    "have incompatible dimensions.",
+                                    cimg_instance,
+                                    elevation._width,elevation._height,elevation._depth,elevation._spectrum,elevation._data);
+      if (is_empty()) return *this;
+      float m, M = (float)max_min(m);
+      if (M==m) ++M;
+      colors.assign();
+      const unsigned int size_x1 = _width - 1, size_y1 = _height - 1;
+      for (unsigned int y = 0; y<size_y1; ++y)
+        for (unsigned int x = 0; x<size_x1; ++x) {
+          const unsigned char
+            r = (unsigned char)(((*this)(x,y,0) - m)*255/(M-m)),
+            g = _spectrum>1?(unsigned char)(((*this)(x,y,1) - m)*255/(M-m)):r,
+            b = _spectrum>2?(unsigned char)(((*this)(x,y,2) - m)*255/(M-m)):(_spectrum>1?0:r);
+          CImg<tc>::vector((tc)r,(tc)g,(tc)b).move_to(colors);
+        }
+      const typename CImg<te>::_functor2d_int func(elevation);
+      return elevation3d(primitives,func,0,0,_width-1.0f,_height-1.0f,_width,_height);
+    }
+
+    //! Create and return the 3d projection planes of the image instance.
+    template<typename tf, typename tc>
+    CImg<floatT> get_projections3d(CImgList<tf>& primitives, CImgList<tc>& colors,
+                                   const unsigned int x0, const unsigned int y0, const unsigned int z0,
+                                   const bool normalize_colors=false) const {
+      float m = 0, M = 0, delta = 1;
+      if (normalize_colors) { m = (float)min_max(M); delta = 255/(m==M?1:M-m); }
+      const unsigned int
+        _x0 = (x0>=_width)?_width - 1:x0,
+        _y0 = (y0>=_height)?_height - 1:y0,
+        _z0 = (z0>=_depth)?_depth - 1:z0;
+      CImg<tc> img_xy, img_xz, img_yz;
+      if (normalize_colors) {
+        ((get_crop(0,0,_z0,0,_width-1,_height-1,_z0,_spectrum-1)-=m)*=delta).move_to(img_xy);
+        ((get_crop(0,_y0,0,0,_width-1,_y0,_depth-1,_spectrum-1)-=m)*=delta).resize(_width,_depth,1,-100,-1).move_to(img_xz);
+        ((get_crop(_x0,0,0,0,_x0,_height-1,_depth-1,_spectrum-1)-=m)*=delta).resize(_height,_depth,1,-100,-1).move_to(img_yz);
+      } else {
+        get_crop(0,0,_z0,0,_width-1,_height-1,_z0,_spectrum-1).move_to(img_xy);
+        get_crop(0,_y0,0,0,_width-1,_y0,_depth-1,_spectrum-1).resize(_width,_depth,1,-100,-1).move_to(img_xz);
+        get_crop(_x0,0,0,0,_x0,_height-1,_depth-1,_spectrum-1).resize(_height,_depth,1,-100,-1).move_to(img_yz);
+      }
+      CImg<floatT> points(12,3,1,1,
+                          0,_width-1,_width-1,0,   0,_width-1,_width-1,0, _x0,_x0,_x0,_x0,
+                          0,0,_height-1,_height-1, _y0,_y0,_y0,_y0,       0,_height-1,_height-1,0,
+                          _z0,_z0,_z0,_z0,         0,0,_depth-1,_depth-1, 0,0,_depth-1,_depth-1);
+      primitives.assign();
+      CImg<tf>::vector(0,1,2,3,0,0,img_xy._width-1,0,img_xy._width-1,img_xy._height-1,0,img_xy._height-1).move_to(primitives);
+      CImg<tf>::vector(4,5,6,7,0,0,img_xz._width-1,0,img_xz._width-1,img_xz._height-1,0,img_xz._height-1).move_to(primitives);
+      CImg<tf>::vector(8,9,10,11,0,0,img_yz._width-1,0,img_yz._width-1,img_yz._height-1,0,img_yz._height-1).move_to(primitives);
+      colors.assign();
+      img_xy.move_to(colors);
+      img_xz.move_to(colors);
+      img_yz.move_to(colors);
+      return points;
+    }
+
+    //! Create and return a isoline of the image instance as a 3d object.
+    /**
+       \param[out] primitives The returned list of the 3d object primitives
+                              (template type \e tf should be at least \e unsigned \e int).
+       \param isovalue The returned list of the 3d object colors.
+       \param size_x The number of subdivisions along the X-axis.
+       \param size_y The number of subdisivions along the Y-axis.
+       \return The N vertices (xi,yi,zi) of the 3d object as a Nx3 CImg<float> image (0<=i<=N-1).
+       \par Example
+       \code
+       const CImg<float> img("reference.jpg");
+       CImgList<unsigned int> faces3d;
+       const CImg<float> points3d = img.get_isoline3d(faces3d,100);
+       CImg<unsigned char>().display_object3d("Isoline3d",points3d,faces3d,colors3d);
+       \endcode
+       \image html ref_isoline3d.jpg
+    **/
+    template<typename tf>
+    CImg<floatT> get_isoline3d(CImgList<tf>& primitives, const float isovalue,
+                               const int size_x=-100, const int size_y=-100) const {
+      if (_spectrum>1)
+        throw CImgInstanceException(_cimg_instance
+                                    "get_isoline3d() : Instance is not a scalar image.",
+                                    cimg_instance);
+      if (_depth>1)
+        throw CImgInstanceException(_cimg_instance
+                                    "get_isoline3d() : Instance is not a 2d image.",
+                                    cimg_instance);
+      primitives.assign();
+      if (is_empty()) return *this;
+      CImg<floatT> vertices;
+      if ((size_x==-100 && size_y==-100) || (size_x==width() && size_y==height())) {
+        const _functor2d_int func(*this);
+        vertices = isoline3d(primitives,func,isovalue,0,0,width()-1.0f,height()-1.0f,width(),height());
+      } else {
+        const _functor2d_float func(*this);
+        vertices = isoline3d(primitives,func,isovalue,0,0,width()-1.0f,height()-1.0f,size_x,size_y);
+      }
+      return vertices;
+    }
+
+    //! Create and return a isosurface of the image instance as a 3d object.
+    /**
+       \param[out] primitives The returned list of the 3d object primitives
+                              (template type \e tf should be at least \e unsigned \e int).
+       \param isovalue The returned list of the 3d object colors.
+       \param size_x The number of subdivisions along the X-axis.
+       \param size_y The number of subdisivions along the Y-axis.
+       \return The N vertices (xi,yi,zi) of the 3d object as a Nx3 CImg<float> image (0<=i<=N-1).
+       \par Example
+       \code
+       const CImg<float> img = CImg<unsigned char>("reference.jpg").resize(-100,-100,20);
+       CImgList<unsigned int> faces3d;
+       const CImg<float> points3d = img.get_isosurface3d(faces3d,100);
+       CImg<unsigned char>().display_object3d("Isosurface3d",points3d,faces3d,colors3d);
+       \endcode
+       \image html ref_isosurface3d.jpg
+    **/
+    template<typename tf>
+    CImg<floatT> get_isosurface3d(CImgList<tf>& primitives, const float isovalue,
+                                  const int size_x=-100, const int size_y=-100, const int size_z=-100) const {
+      if (_spectrum>1)
+        throw CImgInstanceException(_cimg_instance
+                                    "get_isosurface3d() : Instance is not a scalar image.",
+                                    cimg_instance);
+      primitives.assign();
+      if (is_empty()) return *this;
+      CImg<floatT> vertices;
+      if ((size_x==-100 && size_y==-100 && size_z==-100) || (size_x==width() && size_y==height() && size_z==depth())) {
+        const _functor3d_int func(*this);
+        vertices = isosurface3d(primitives,func,isovalue,0,0,0,width()-1.0f,height()-1.0f,depth()-1.0f,width(),height(),depth());
+      } else {
+        const _functor3d_float func(*this);
+        vertices = isosurface3d(primitives,func,isovalue,0,0,0,width()-1.0f,height()-1.0f,depth()-1.0f,size_x,size_y,size_z);
+      }
+      return vertices;
+    }
+
+    //! Return elevation3d of a function.
+    template<typename tf, typename tfunc>
+    static CImg<floatT> elevation3d(CImgList<tf>& primitives, const tfunc& func,
+                                    const float x0, const float y0, const float x1, const float y1,
+                                    const int size_x=256, const int size_y=256) {
+      const float
+        nx0 = x0<x1?x0:x1, ny0 = y0<y1?y0:y1,
+        nx1 = x0<x1?x1:x0, ny1 = y0<y1?y1:y0;
+      const unsigned int
+        _nsize_x = (unsigned int)(size_x>=0?size_x:(nx1-nx0)*-size_x/100), nsize_x = _nsize_x?_nsize_x:1, nsize_x1 = nsize_x - 1,
+        _nsize_y = (unsigned int)(size_y>=0?size_y:(ny1-ny0)*-size_y/100), nsize_y = _nsize_y?_nsize_y:1, nsize_y1 = nsize_y - 1;
+      if (nsize_x<2 || nsize_y<2)
+        throw CImgArgumentException("CImg<%s>::elevation3d() : Invalid specified size (%d,%d).",
+                                    pixel_type(),
+                                    nsize_x,nsize_y);
+
+      CImg<floatT> vertices(nsize_x*nsize_y,3);
+      floatT *ptr_x = vertices.data(0,0), *ptr_y = vertices.data(0,1), *ptr_z = vertices.data(0,2);
+      for (unsigned int y = 0; y<nsize_y; ++y) {
+        const float Y = ny0 + y*(ny1-ny0)/nsize_y1;
+        for (unsigned int x = 0; x<nsize_x; ++x) {
+          const float X = nx0 + x*(nx1-nx0)/nsize_x1;
+          *(ptr_x++) = (float)x;
+          *(ptr_y++) = (float)y;
+          *(ptr_z++) = (float)func(X,Y);
+        }
+      }
+      primitives.assign(nsize_x1*nsize_y1,1,4);
+      for (unsigned int p = 0, y = 0; y<nsize_y1; ++y) {
+        const unsigned int yw = y*nsize_x;
+        for (unsigned int x = 0; x<nsize_x1; ++x) {
+          const unsigned int xpyw = x + yw, xpyww = xpyw + nsize_x;
+          primitives[p++].fill(xpyw,xpyww,xpyww+1,xpyw+1);
+        }
+      }
+      return vertices;
+    }
+
+    template<typename tf>
+    static CImg<floatT> elevation3d(CImgList<tf>& primitives, const char *const expression,
+                                    const float x0, const float y0, const float x1, const float y1,
+                                    const int sizex=256, const int sizey=256) {
+      const _functor2d_expr func(expression);
+      return elevation3d(primitives,func,x0,y0,x1,y1,sizex,sizey);
+    }
+
+    //! Return isoline as a 3d object.
+    template<typename tf, typename tfunc>
+    static CImg<floatT> isoline3d(CImgList<tf>& primitives, const tfunc& func, const float isovalue,
+                                  const float x0, const float y0, const float x1, const float y1,
+                                  const int sizex=256, const int sizey=256) {
+      static const unsigned int edges[16] = { 0x0, 0x9, 0x3, 0xa, 0x6, 0xf, 0x5, 0xc, 0xc, 0x5, 0xf, 0x6, 0xa, 0x3, 0x9, 0x0 };
+      static const 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)(sizex>=0?sizex:cimg::round((x1-x0)*-sizex/100 + 1)),
+        _ny = (unsigned int)(sizey>=0?sizey:cimg::round((y1-y0)*-sizey/100 + 1)),
+        nx = _nx?_nx:1,
+        ny = _ny?_ny:1,
+        nxm1 = nx - 1,
+        nym1 = ny - 1;
+      primitives.assign();
+      if (!nxm1 || !nym1) return CImg<floatT>();
+      const float dx = (x1 - x0)/nxm1, dy = (y1 - y0)/nym1;
+      CImgList<floatT> vertices;
+      CImg<intT> indices1(nx,1,1,2,-1), indices2(nx,1,1,2);
+      CImg<floatT> values1(nx), values2(nx);
+      float X = x0, Y = y0, nX = X + dx, nY = Y + dy;
+
+      // Fill first line with values
+      cimg_forX(values1,x) { values1(x) = (float)func(X,Y); X+=dx; }
+
+      // Run the marching squares algorithm
+      for (unsigned int yi = 0, nyi = 1; yi<nym1; ++yi, ++nyi, Y=nY, nY+=dy) {
+        X = x0; nX = X + dx;
+        indices2.fill(-1);
+        for (unsigned int xi = 0, nxi = 1; xi<nxm1; ++xi, ++nxi, X=nX, nX+=dx) {
+
+          // Determine square 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 vertices
+          if (edge) {
+            if ((edge&1) && indices1(xi,0)<0) {
+              const float Xi = X + (isovalue-val0)*dx/(val1-val0);
+              indices1(xi,0) = vertices._width;
+              CImg<floatT>::vector(Xi,Y,0).move_to(vertices);
+            }
+            if ((edge&2) && indices1(nxi,1)<0) {
+              const float Yi = Y + (isovalue-val1)*dy/(val2-val1);
+              indices1(nxi,1) = vertices._width;
+              CImg<floatT>::vector(nX,Yi,0).move_to(vertices);
+            }
+            if ((edge&4) && indices2(xi,0)<0) {
+              const float Xi = X + (isovalue-val3)*dx/(val2-val3);
+              indices2(xi,0) = vertices._width;
+              CImg<floatT>::vector(Xi,nY,0).move_to(vertices);
+            }
+            if ((edge&8) && indices1(xi,1)<0) {
+              const float Yi = Y + (isovalue-val0)*dy/(val3-val0);
+              indices1(xi,1) = vertices._width;
+              CImg<floatT>::vector(X,Yi,0).move_to(vertices);
+            }
+
+            // Create segments
+            for (const int *segment = segments[configuration]; *segment!=-1; ) {
+              const unsigned int p0 = *(segment++), p1 = *(segment++);
+              const tf
+                i0 = (tf)(_isoline3d_indice(p0,indices1,indices2,xi,nxi)),
+                i1 = (tf)(_isoline3d_indice(p1,indices1,indices2,xi,nxi));
+              CImg<tf>::vector(i0,i1).move_to(primitives);
+            }
+          }
+        }
+        values1.swap(values2);
+        indices1.swap(indices2);
+      }
+      return vertices>'x';
+    }
+
+    template<typename tf>
+    static CImg<floatT> isoline3d(CImgList<tf>& primitives, const char *const expression, const float isovalue,
+                                  const float x0, const float y0, const float x1, const float y1,
+                                  const int sizex=256, const int sizey=256) {
+      const _functor2d_expr func(expression);
+      return isoline3d(primitives,func,isovalue,x0,y0,x1,y1,sizex,sizey);
+    }
+
+    template<typename t>
+    static int _isoline3d_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;
+    }
+
+    //! Return isosurface as a 3d object.
+    template<typename tf, typename tfunc>
+    static CImg<floatT> isosurface3d(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 int size_x=32, const int size_y=32, const int size_z=32) {
+      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)(size_x>=0?size_x:cimg::round((x1-x0)*-size_x/100 + 1)),
+        _ny = (unsigned int)(size_y>=0?size_y:cimg::round((y1-y0)*-size_y/100 + 1)),
+        _nz = (unsigned int)(size_z>=0?size_z:cimg::round((z1-z0)*-size_z/100 + 1)),
+        nx = _nx?_nx:1,
+        ny = _ny?_ny:1,
+        nz = _nz?_nz:1,
+        nxm1 = nx - 1,
+        nym1 = ny - 1,
+        nzm1 = nz - 1;
+      primitives.assign();
+      if (!nxm1 || !nym1 || !nzm1) return CImg<floatT>();
+      const float dx = (x1 - x0)/nxm1, dy = (y1 - y0)/nym1, dz = (z1 - z0)/nzm1;
+      CImgList<floatT> vertices;
+      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+=dx; }
+        Y+=dy;
+      }
+
+      // Run Marching Cubes algorithm
+      Z = z0; nZ = Z + dz;
+      for (unsigned int zi = 0; zi<nzm1; ++zi, Z = nZ, nZ+=dz) {
+        Y = y0; nY = Y + dy;
+        indices2.fill(-1);
+        for (unsigned int yi = 0, nyi = 1; yi<nym1; ++yi, ++nyi, Y = nY, nY+=dy) {
+          X = x0; nX = X + dx;
+          for (unsigned int xi = 0, nxi = 1; xi<nxm1; ++xi, ++nxi, X = nX, nX+=dx) {
+
+            // 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 vertices
+            if (edge) {
+              if ((edge&1) && indices1(xi,yi,0)<0) {
+                const float Xi = X + (isovalue-val0)*dx/(val1-val0);
+                indices1(xi,yi,0) = vertices._width;
+                CImg<floatT>::vector(Xi,Y,Z).move_to(vertices);
+              }
+              if ((edge&2) && indices1(nxi,yi,1)<0) {
+                const float Yi = Y + (isovalue-val1)*dy/(val2-val1);
+                indices1(nxi,yi,1) = vertices._width;
+                CImg<floatT>::vector(nX,Yi,Z).move_to(vertices);
+              }
+              if ((edge&4) && indices1(xi,nyi,0)<0) {
+                const float Xi = X + (isovalue-val3)*dx/(val2-val3);
+                indices1(xi,nyi,0) = vertices._width;
+                CImg<floatT>::vector(Xi,nY,Z).move_to(vertices);
+              }
+              if ((edge&8) && indices1(xi,yi,1)<0) {
+                const float Yi = Y + (isovalue-val0)*dy/(val3-val0);
+                indices1(xi,yi,1) = vertices._width;
+                CImg<floatT>::vector(X,Yi,Z).move_to(vertices);
+              }
+              if ((edge&16) && indices2(xi,yi,0)<0) {
+                const float Xi = X + (isovalue-val4)*dx/(val5-val4);
+                indices2(xi,yi,0) = vertices._width;
+                CImg<floatT>::vector(Xi,Y,nZ).move_to(vertices);
+              }
+              if ((edge&32) && indices2(nxi,yi,1)<0) {
+                const float Yi = Y + (isovalue-val5)*dy/(val6-val5);
+                indices2(nxi,yi,1) = vertices._width;
+                CImg<floatT>::vector(nX,Yi,nZ).move_to(vertices);
+              }
+              if ((edge&64) && indices2(xi,nyi,0)<0) {
+                const float Xi = X + (isovalue-val7)*dx/(val6-val7);
+                indices2(xi,nyi,0) = vertices._width;
+                CImg<floatT>::vector(Xi,nY,nZ).move_to(vertices);
+              }
+              if ((edge&128) && indices2(xi,yi,1)<0)  {
+                const float Yi = Y + (isovalue-val4)*dy/(val7-val4);
+                indices2(xi,yi,1) = vertices._width;
+                CImg<floatT>::vector(X,Yi,nZ).move_to(vertices);
+              }
+              if ((edge&256) && indices1(xi,yi,2)<0) {
+                const float Zi = Z+ (isovalue-val0)*dz/(val4-val0);
+                indices1(xi,yi,2) = vertices._width;
+                CImg<floatT>::vector(X,Y,Zi).move_to(vertices);
+              }
+              if ((edge&512) && indices1(nxi,yi,2)<0)  {
+                const float Zi = Z + (isovalue-val1)*dz/(val5-val1);
+                indices1(nxi,yi,2) = vertices._width;
+                CImg<floatT>::vector(nX,Y,Zi).move_to(vertices);
+              }
+              if ((edge&1024) && indices1(nxi,nyi,2)<0) {
+                const float Zi = Z + (isovalue-val2)*dz/(val6-val2);
+                indices1(nxi,nyi,2) = vertices._width;
+                CImg<floatT>::vector(nX,nY,Zi).move_to(vertices);
+              }
+              if ((edge&2048) && indices1(xi,nyi,2)<0) {
+                const float Zi = Z + (isovalue-val3)*dz/(val7-val3);
+                indices1(xi,nyi,2) = vertices._width;
+                CImg<floatT>::vector(X,nY,Zi).move_to(vertices);
+              }
+
+              // Create triangles
+              for (const int *triangle = triangles[configuration]; *triangle!=-1; ) {
+                const unsigned int p0 = *(triangle++), p1 = *(triangle++), p2 = *(triangle++);
+                const tf
+                  i0 = (tf)(_isosurface3d_indice(p0,indices1,indices2,xi,yi,nxi,nyi)),
+                  i1 = (tf)(_isosurface3d_indice(p1,indices1,indices2,xi,yi,nxi,nyi)),
+                  i2 = (tf)(_isosurface3d_indice(p2,indices1,indices2,xi,yi,nxi,nyi));
+                CImg<tf>::vector(i0,i2,i1).move_to(primitives);
+              }
+            }
+          }
+        }
+        cimg::swap(values1,values2);
+        cimg::swap(indices1,indices2);
+      }
+      return vertices>'x';
+    }
+
+    template<typename tf>
+    static CImg<floatT> isosurface3d(CImgList<tf>& primitives, const char *const expression, const float isovalue,
+                                     const float x0, const float y0, const float z0,
+                                     const float x1, const float y1, const float z1,
+                                     const int dx=32, const int dy=32, const int dz=32) {
+      const _functor3d_expr func(expression);
+      return isosurface3d(primitives,func,isovalue,x0,y0,z0,x1,y1,z1,dx,dy,dz);
+    }
+
+    template<typename t>
+    static int _isosurface3d_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;
+    }
+
+    // Define functors for accessing image values (used in previous functions).
+    struct _functor2d_int {
+      const CImg<T>& ref;
+      _functor2d_int(const CImg<T>& pref):ref(pref) {}
+      float operator()(const float x, const float y) const {
+        return (float)ref((int)x,(int)y);
+      }
+    };
+
+    struct _functor2d_float {
+      const CImg<T>& ref;
+      _functor2d_float(const CImg<T>& pref):ref(pref) {}
+      float operator()(const float x, const float y) const {
+        return (float)ref._linear_atXY(x,y);
+      }
+    };
+
+    struct _functor2d_expr {
+      _cimg_math_parser *mp;
+      _functor2d_expr(const char *const expr):mp(0) { mp = new _cimg_math_parser(CImg<T>::empty(),expr,0); }
+      ~_functor2d_expr() { delete mp; }
+      float operator()(const float x, const float y) const {
+        return (float)mp->eval(x,y,0,0);
+      }
+    };
+
+    struct _functor3d_int {
+      const CImg<T>& ref;
+      _functor3d_int(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 _functor3d_float {
+      const CImg<T>& ref;
+      _functor3d_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);
+      }
+    };
+
+    struct _functor3d_expr {
+      _cimg_math_parser *mp;
+      ~_functor3d_expr() { delete mp; }
+      _functor3d_expr(const char *const expr):mp(0) { mp = new _cimg_math_parser(CImg<T>::empty(),expr,0); }
+      float operator()(const float x, const float y, const float z) const {
+        return (float)mp->eval(x,y,z,0);
+      }
+    };
+
+    struct _functor4d_int {
+      const CImg<T>& ref;
+      _functor4d_int(const CImg<T>& pref):ref(pref) {}
+      float operator()(const float x, const float y, const float z, const unsigned int c) const {
+        return (float)ref((int)x,(int)y,(int)z,c);
+      }
+    };
+
+    //! Create and return a 3d box object.
+    /**
+       \param[out] primitives The returned list of the 3d object primitives
+                              (template type \e tf should be at least \e unsigned \e int).
+       \param size_x The width of the box (dimension along the X-axis).
+       \param size_y The height of the box (dimension along the Y-axis).
+       \param size_z The depth of the box (dimension along the Z-axis).
+       \return The N vertices (xi,yi,zi) of the 3d object as a Nx3 CImg<float> image (0<=i<=N-1).
+       \par Example
+       \code
+       CImgList<unsigned int> faces3d;
+       const CImg<float> points3d = CImg<float>::box3d(faces3d,10,20,30);
+       CImg<unsigned char>().display_object3d("Box3d",points3d,faces3d);
+       \endcode
+       \image html ref_box3d.jpg
+    **/
+    template<typename tf>
+    static CImg<floatT> box3d(CImgList<tf>& primitives,
+                              const float size_x=200, const float size_y=100, const float size_z=100) {
+      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,
+                          0.,size_x,size_x,    0.,    0.,size_x,size_x,    0.,
+                          0.,    0.,size_y,size_y,    0.,    0.,size_y,size_y,
+                          0.,    0.,    0.,    0.,size_z,size_z,size_z,size_z);
+    }
+
+    //! Create and return a 3d cone.
+    /**
+       \param[out] primitives The returned list of the 3d object primitives
+                              (template type \e tf should be at least \e unsigned \e int).
+       \param radius The radius of the cone basis.
+       \param size_z The cone's height.
+       \param subdivisions The number of basis angular subdivisions.
+       \return The N vertices (xi,yi,zi) of the 3d object as a Nx3 CImg<float> image (0<=i<=N-1).
+       \par Example
+       \code
+       CImgList<unsigned int> faces3d;
+       const CImg<float> points3d = CImg<float>::cone3d(faces3d,50);
+       CImg<unsigned char>().display_object3d("Cone3d",points3d,faces3d);
+       \endcode
+       \image html ref_cone3d.jpg
+    **/
+    template<typename tf>
+    static CImg<floatT> cone3d(CImgList<tf>& primitives,
+                               const float radius=50, const float size_z=100, const unsigned int subdivisions=24) {
+      primitives.assign();
+      if (!subdivisions) return CImg<floatT>();
+      CImgList<floatT> vertices(2,1,3,1,1,
+                                0.,0.,size_z,
+                                0.,0.,0.);
+      for (float delta = 360.0f/subdivisions, angle = 0; angle<360; angle+=delta) {
+        const float a = (float)(angle*cimg::PI/180);
+        CImg<floatT>::vector((float)(radius*std::cos(a)),(float)(radius*std::sin(a)),0).move_to(vertices);
+      }
+      const unsigned int nbr = vertices._width - 2;
+      for (unsigned int p = 0; p<nbr; ++p) {
+        const unsigned int curr = 2 + p, next = 2 + ((p+1)%nbr);
+        CImg<tf>::vector(1,next,curr).move_to(primitives);
+        CImg<tf>::vector(0,curr,next).move_to(primitives);
+      }
+      return vertices>'x';
+    }
+
+    //! Create and return a 3d cylinder.
+    /**
+       \param[out] primitives The returned list of the 3d object primitives
+                              (template type \e tf should be at least \e unsigned \e int).
+       \param radius The radius of the cylinder basis.
+       \param size_z The cylinder's height.
+       \param subdivisions The number of basis angular subdivisions.
+       \return The N vertices (xi,yi,zi) of the 3d object as a Nx3 CImg<float> image (0<=i<=N-1).
+       \par Example
+       \code
+       CImgList<unsigned int> faces3d;
+       const CImg<float> points3d = CImg<float>::cylinder3d(faces3d,50);
+       CImg<unsigned char>().display_object3d("Cylinder3d",points3d,faces3d);
+       \endcode
+       \image html ref_cylinder3d.jpg
+    **/
+    template<typename tf>
+    static CImg<floatT> cylinder3d(CImgList<tf>& primitives,
+                                   const float radius=50, const float size_z=100, const unsigned int subdivisions=24) {
+      primitives.assign();
+      if (!subdivisions) return CImg<floatT>();
+      CImgList<floatT> vertices(2,1,3,1,1,
+                                0.,0.,0.,
+                                0.,0.,size_z);
+      for (float delta = 360.0f/subdivisions, angle = 0; angle<360; angle+=delta) {
+        const float a = (float)(angle*cimg::PI/180);
+        CImg<floatT>::vector((float)(radius*std::cos(a)),(float)(radius*std::sin(a)),0.0f).move_to(vertices);
+        CImg<floatT>::vector((float)(radius*std::cos(a)),(float)(radius*std::sin(a)),size_z).move_to(vertices);
+      }
+      const unsigned int nbr = (vertices._width - 2)/2;
+      for (unsigned int p = 0; p<nbr; ++p) {
+        const unsigned int curr = 2+2*p, next = 2+(2*((p+1)%nbr));
+        CImg<tf>::vector(0,next,curr).move_to(primitives);
+        CImg<tf>::vector(1,curr+1,next+1).move_to(primitives);
+        CImg<tf>::vector(curr,next,next+1,curr+1).move_to(primitives);
+      }
+      return vertices>'x';
+    }
+
+    //! Create and return a 3d torus.
+    /**
+       \param[out] primitives The returned list of the 3d object primitives
+                              (template type \e tf should be at least \e unsigned \e int).
+       \param radius1 The large radius.
+       \param radius2 The small radius.
+       \param subdivisions1 The number of angular subdivisions for the large radius.
+       \param subdivisions2 The number of angular subdivisions for the small radius.
+       \return The N vertices (xi,yi,zi) of the 3d object as a Nx3 CImg<float> image (0<=i<=N-1).
+       \par Example
+       \code
+       CImgList<unsigned int> faces3d;
+       const CImg<float> points3d = CImg<float>::torus3d(faces3d,20,4);
+       CImg<unsigned char>().display_object3d("Torus3d",points3d,faces3d);
+       \endcode
+       \image html ref_torus3d.jpg
+    **/
+    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> vertices;
+      for (unsigned int v = 0; v<subdivisions1; ++v) {
+        const float
+          beta = (float)(v*2*cimg::PI/subdivisions1),
+          xc = radius1*(float)std::cos(beta),
+          yc = radius1*(float)std::sin(beta);
+        for (unsigned int u = 0; u<subdivisions2; ++u) {
+          const float
+            alpha = (float)(u*2*cimg::PI/subdivisions2),
+            x = xc + radius2*(float)(std::cos(alpha)*std::cos(beta)),
+            y = yc + radius2*(float)(std::cos(alpha)*std::sin(beta)),
+            z = radius2*(float)std::sin(alpha);
+          CImg<floatT>::vector(x,y,z).move_to(vertices);
+        }
+      }
+      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;
+          CImg<tf>::vector(svv+nu,svv+uu,snv+uu).move_to(primitives);
+          CImg<tf>::vector(svv+nu,snv+uu,snv+nu).move_to(primitives);
+        }
+      }
+      return vertices>'x';
+    }
+
+    //! Create and return a 3d XY-plane.
+    /**
+       \param[out] primitives The returned list of the 3d object primitives
+                              (template type \e tf should be at least \e unsigned \e int).
+       \param size_x The width of the plane (dimension along the X-axis).
+       \param size_y The height of the plane (dimensions along the Y-axis).
+       \param subdivisions_x The number of planar subdivisions along the X-axis.
+       \param subdivisions_y The number of planar subdivisions along the Y-axis.
+       \return The N vertices (xi,yi,zi) of the 3d object as a Nx3 CImg<float> image (0<=i<=N-1).
+       \par Example
+       \code
+       CImgList<unsigned int> faces3d;
+       const CImg<float> points3d = CImg<float>::plane3d(faces3d,100,50);
+       CImg<unsigned char>().display_object3d("Plane3d",points3d,faces3d);
+       \endcode
+       \image html ref_plane3d.jpg
+    **/
+    template<typename tf>
+    static CImg<floatT> plane3d(CImgList<tf>& primitives,
+                                const float size_x=100, const float size_y=100,
+                                const unsigned int subdivisions_x=10, const unsigned int subdivisions_y=10) {
+      primitives.assign();
+      if (!subdivisions_x || !subdivisions_y) return CImg<floatT>();
+      CImgList<floatT> vertices;
+      const unsigned int w = subdivisions_x + 1, h = subdivisions_y + 1;
+      const float fx = (float)size_x/w, fy = (float)size_y/h;
+      for (unsigned int y = 0; y<h; ++y) for (unsigned int x = 0; x<w; ++x)
+        CImg<floatT>::vector(fx*x,fy*y,0).move_to(vertices);
+      for (unsigned int y = 0; y<subdivisions_y; ++y) for (unsigned int x = 0; x<subdivisions_x; ++x) {
+        const int off1 = x+y*w, off2 = x+1+y*w, off3 = x+1+(y+1)*w, off4 = x+(y+1)*w;
+        CImg<tf>::vector(off1,off4,off3,off2).move_to(primitives);
+      }
+      return vertices>'x';
+    }
+
+    //! Create and return a 3d sphere.
+    /**
+       \param[out] primitives The returned list of the 3d object primitives
+                              (template type \e tf should be at least \e unsigned \e int).
+       \param radius The radius of the sphere (dimension along the X-axis).
+       \param subdivisions The number of recursive subdivisions from an initial icosahedron.
+       \return The N vertices (xi,yi,zi) of the 3d object as a Nx3 CImg<float> image (0<=i<=N-1).
+       \par Example
+       \code
+       CImgList<unsigned int> faces3d;
+       const CImg<float> points3d = CImg<float>::sphere3d(faces3d,100,4);
+       CImg<unsigned char>().display_object3d("Sphere3d",points3d,faces3d);
+       \endcode
+       \image html ref_sphere3d.jpg
+    **/
+    template<typename tf>
+    static CImg<floatT> sphere3d(CImgList<tf>& primitives,
+                                 const float radius=50, const unsigned int subdivisions=3) {
+
+      // Create initial icosahedron
+      primitives.assign();
+      const double tmp = (1+std::sqrt(5.0f))/2, a = 1.0/std::sqrt(1+tmp*tmp), b = tmp*a;
+      CImgList<floatT> vertices(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);
+      // edge - length/2
+      float he = (float)a;
+
+      // Recurse subdivisions
+      for (unsigned int i = 0; i<subdivisions; ++i) {
+        const unsigned int L = primitives._width;
+        he/=2;
+        const float he2 = he*he;
+        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 = vertices(p0,0), y0 = vertices(p0,1), z0 = vertices(p0,2),
+            x1 = vertices(p1,0), y1 = vertices(p1,1), z1 = vertices(p1,2),
+            x2 = vertices(p2,0), y2 = vertices(p2,1), z2 = vertices(p2,2),
+            tnx0 = (x0+x1)/2, tny0 = (y0+y1)/2, tnz0 = (z0+z1)/2, nn0 = (float)std::sqrt(tnx0*tnx0+tny0*tny0+tnz0*tnz0),
+            tnx1 = (x0+x2)/2, tny1 = (y0+y2)/2, tnz1 = (z0+z2)/2, nn1 = (float)std::sqrt(tnx1*tnx1+tny1*tny1+tnz1*tnz1),
+            tnx2 = (x1+x2)/2, tny2 = (y1+y2)/2, tnz2 = (z1+z2)/2, nn2 = (float)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(vertices,p) {
+            const float x = (float)vertices(p,0), y = (float)vertices(p,1), z = (float)vertices(p,2);
+            if (cimg::sqr(x-nx0) + cimg::sqr(y-ny0) + cimg::sqr(z-nz0)<he2) i0 = p;
+            if (cimg::sqr(x-nx1) + cimg::sqr(y-ny1) + cimg::sqr(z-nz1)<he2) i1 = p;
+            if (cimg::sqr(x-nx2) + cimg::sqr(y-ny2) + cimg::sqr(z-nz2)<he2) i2 = p;
+          }
+          if (i0<0) { CImg<floatT>::vector(nx0,ny0,nz0).move_to(vertices); i0 = vertices._width - 1; }
+          if (i1<0) { CImg<floatT>::vector(nx1,ny1,nz1).move_to(vertices); i1 = vertices._width - 1; }
+          if (i2<0) { CImg<floatT>::vector(nx2,ny2,nz2).move_to(vertices); i2 = vertices._width - 1; }
+          primitives.remove(0);
+          CImg<tf>::vector(p0,i0,i1).move_to(primitives);
+          CImg<tf>::vector((tf)i0,(tf)p1,(tf)i2).move_to(primitives);
+          CImg<tf>::vector((tf)i1,(tf)i2,(tf)p2).move_to(primitives);
+          CImg<tf>::vector((tf)i1,(tf)i0,(tf)i2).move_to(primitives);
+        }
+      }
+      return (vertices>'x')*=radius;
+    }
+
+    //! Create and return a 3d ellipsoid.
+    /**
+       \param[out] primitives The returned list of the 3d object primitives
+                              (template type \e tf should be at least \e unsigned \e int).
+       \param tensor The tensor which gives the shape and size of the ellipsoid.
+       \param subdivisions The number of recursive subdivisions from an initial stretched icosahedron.
+       \return The N vertices (xi,yi,zi) of the 3d object as a Nx3 CImg<float> image (0<=i<=N-1).
+       \par Example
+       \code
+       CImgList<unsigned int> faces3d;
+       const CImg<float> tensor = CImg<float>::diagonal(10,7,3),
+                         points3d = CImg<float>::ellipsoid3d(faces3d,tensor,4);
+       CImg<unsigned char>().display_object3d("Ellipsoid3d",points3d,faces3d);
+       \endcode
+       \image html ref_ellipsoid3d.jpg
+    **/
+    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>();
+      CImg<floatT> S, V;
+      tensor.symmetric_eigen(S,V);
+      const float orient =
+        (V(0,1)*V(1,2) - V(0,2)*V(1,1))*V(2,0) +
+        (V(0,2)*V(1,0) - V(0,0)*V(1,2))*V(2,1) +
+        (V(0,0)*V(1,1) - V(0,1)*V(1,0))*V(2,2);
+      if (orient<0) { V(2,0) = -V(2,0); V(2,1) = -V(2,1); V(2,2) = -V(2,2); }
+      const float l0 = S[0], l1 = S[1], l2 = S[2];
+      CImg<floatT> vertices = sphere3d(primitives,1.0,subdivisions);
+      vertices.get_shared_line(0)*=l0;
+      vertices.get_shared_line(1)*=l1;
+      vertices.get_shared_line(2)*=l2;
+      return V*vertices;
+    }
+
+    //! Convert a 3d object into a CImg3d.
+    template<typename tp, typename tc, typename to>
+    CImg<T>& object3dtoCImg3d(const CImgList<tp>& primitives,
+                              const CImgList<tc>& colors,
+                              const to& opacities) {
+      return get_object3dtoCImg3d(primitives,colors,opacities).move_to(*this);
+    }
+
+    template<typename tp, typename tc>
+    CImg<T>& object3dtoCImg3d(const CImgList<tp>& primitives,
+                              const CImgList<tc>& colors) {
+      return get_object3dtoCImg3d(primitives,colors).move_to(*this);
+    }
+
+    template<typename tp>
+    CImg<T>& object3dtoCImg3d(const CImgList<tp>& primitives) {
+      return get_object3dtoCImg3d(primitives).move_to(*this);
+    }
+
+    CImg<T>& object3dtoCImg3d() {
+      return get_object3dtoCImg3d().move_to(*this);
+    }
+
+    template<typename tp, typename tc, typename to>
+    CImg<floatT> get_object3dtoCImg3d(const CImgList<tp>& primitives,
+                                      const CImgList<tc>& colors,
+                                      const to& opacities) const {
+      char error_message[1024] = { 0 };
+      if (!is_object3d(primitives,colors,opacities,true,error_message))
+        throw CImgInstanceException(_cimg_instance
+                                    "object3dtoCImg3d() : Invalid specified 3d object (%u,%u) (%s).",
+                                    cimg_instance,_width,primitives._width,error_message);
+      CImg<floatT> res(1,_size_object3dtoCImg3d(primitives,colors,opacities));
+      float *ptrd = res._data;
+
+      // Put magick number.
+      *(ptrd++) = 'C' + 0.5f; *(ptrd++) = 'I' + 0.5f; *(ptrd++) = 'm' + 0.5f;
+      *(ptrd++) = 'g' + 0.5f; *(ptrd++) = '3' + 0.5f; *(ptrd++) = 'd' + 0.5f;
+
+      // Put number of vertices and primitives.
+      *(ptrd++) = cimg::uint2float(_width);
+      *(ptrd++) = cimg::uint2float(primitives._width);
+
+      // Put vertex data.
+      if (is_empty() || !primitives) return res;
+      const T *ptrx = data(0,0), *ptry = data(0,1), *ptrz = data(0,2);
+      cimg_forX(*this,p) {
+        *(ptrd++) = (float)*(ptrx++);
+        *(ptrd++) = (float)*(ptry++);
+        *(ptrd++) = (float)*(ptrz++);
+      }
+
+      // Put primitive data.
+      cimglist_for(primitives,p) {
+        *(ptrd++) = (float)primitives[p].size();
+        const tp *ptrp = primitives[p]._data;
+        cimg_foroff(primitives[p],i) *(ptrd++) = cimg::uint2float((unsigned int)*(ptrp++));
+      }
+
+      // Put color/texture data.
+      const unsigned int csiz = cimg::min(colors._width,primitives._width);
+      for (int c = 0; c<(int)csiz; ++c) {
+        const CImg<tc>& color = colors[c];
+        const tc *ptrc = color._data;
+        if (color.size()==3) { *(ptrd++) = (float)*(ptrc++); *(ptrd++) = (float)*(ptrc++); *(ptrd++) = (float)*ptrc; }
+        else {
+          *(ptrd++) = -128.0f;
+          int shared_ind = -1;
+          if (color.is_shared()) for (int i = 0; i<c; ++i) if (ptrc==colors[i]._data) { shared_ind = i; break; }
+          if (shared_ind<0) {
+            *(ptrd++) = (float)color._width;
+            *(ptrd++) = (float)color._height;
+            *(ptrd++) = (float)color._spectrum;
+            cimg_foroff(color,l) *(ptrd++) = (float)*(ptrc++);
+          } else {
+            *(ptrd++) = (float)shared_ind;
+            *(ptrd++) = 0;
+            *(ptrd++) = 0;
+          }
+        }
+      }
+      const int csiz2 = primitives._width - colors._width;
+      for (int c = 0; c<csiz2; ++c) { *(ptrd++) = 200.0f; *(ptrd++) = 200.0f; *(ptrd++) = 200.0f; }
+
+      // Put opacity data.
+      ptrd = _object3dtoCImg3d(opacities,ptrd);
+      const float *ptre = res.end();
+      while (ptrd<ptre) *(ptrd++) = 1.0f;
+      return res;
+    }
+
+    template<typename to>
+    float* _object3dtoCImg3d(const CImgList<to>& opacities, float *ptrd) const {
+      cimglist_for(opacities,o) {
+        const CImg<to>& opacity = opacities[o];
+        const to *ptro = opacity._data;
+        if (opacity.size()==1) *(ptrd++) = (float)*ptro;
+        else {
+          *(ptrd++) = -128.0f;
+          int shared_ind = -1;
+          if (opacity.is_shared()) for (int i = 0; i<o; ++i) if (ptro==opacities[i]._data) { shared_ind = i; break; }
+          if (shared_ind<0) {
+            *(ptrd++) = (float)opacity._width;
+            *(ptrd++) = (float)opacity._height;
+            *(ptrd++) = (float)opacity._spectrum;
+            cimg_foroff(opacity,l) *(ptrd++) = (float)*(ptro++);
+          } else {
+            *(ptrd++) = (float)shared_ind;
+            *(ptrd++) = 0;
+            *(ptrd++) = 0;
+          }
+        }
+      }
+      return ptrd;
+    }
+
+    template<typename to>
+    float* _object3dtoCImg3d(const CImg<to>& opacities, float *ptrd) const {
+      const to *ptro = opacities._data;
+      cimg_foroff(opacities,o) *(ptrd++) = (float)*(ptro++);
+      return ptrd;
+    }
+
+    template<typename tp, typename tc, typename to>
+    unsigned int _size_object3dtoCImg3d(const CImgList<tp>& primitives,
+                                        const CImgList<tc>& colors,
+                                        const CImgList<to>& opacities) const {
+      unsigned int siz = 8 + 3*width();
+      cimglist_for(primitives,p) siz+=primitives[p].size() + 1;
+      for (int c = cimg::min(primitives._width,colors._width)-1; c>=0; --c) {
+        if (colors[c].is_shared()) siz+=4;
+        else { const unsigned int csiz = colors[c].size(); siz+=(csiz!=3)?4+csiz:3; }
+      }
+      if (colors._width<primitives._width) siz+=3*(primitives._width - colors._width);
+      cimglist_for(opacities,o) {
+        if (opacities[o].is_shared()) siz+=4;
+        else { const unsigned int osiz = opacities[o].size(); siz+=(osiz!=1)?4+osiz:1; }
+      }
+      siz+=primitives._width - opacities._width;
+      return siz;
+    }
+
+    template<typename tp, typename tc, typename to>
+    unsigned int _size_object3dtoCImg3d(const CImgList<tp>& primitives,
+                                        const CImgList<tc>& colors,
+                                        const CImg<to>& opacities) const {
+      unsigned int siz = 8 + 3*width();
+      cimglist_for(primitives,p) siz+=primitives[p].size() + 1;
+      for (int c = cimg::min(primitives._width,colors._width)-1; c>=0; --c) {
+        const unsigned int csiz = colors[c].size(); siz+=(csiz!=3)?4+csiz:3;
+      }
+      if (colors._width<primitives._width) siz+=3*(primitives._width - colors._width);
+      siz+=primitives.size();
+      cimg::unused(opacities);
+      return siz;
+    }
+
+    template<typename tp, typename tc>
+    CImg<floatT> get_object3dtoCImg3d(const CImgList<tp>& primitives,
+                                      const CImgList<tc>& colors) const {
+      CImgList<T> opacities;
+      return get_object3dtoCImg3d(primitives,colors,opacities);
+    }
+
+    template<typename tp>
+    CImg<floatT> get_object3dtoCImg3d(const CImgList<tp>& primitives) const {
+      CImgList<T> colors, opacities;
+      return get_object3dtoCImg3d(primitives,colors,opacities);
+    }
+
+    CImg<floatT> get_object3dtoCImg3d() const {
+      CImgList<T> opacities, colors;
+      CImgList<uintT> primitives(width(),1,1,1,1);
+      cimglist_for(primitives,p) primitives(p,0) = p;
+      return get_object3dtoCImg3d(primitives,colors,opacities);
+    }
+
+    //! Convert a CImg3d (one-column image) into a 3d object.
+    template<typename tp, typename tc, typename to>
+    CImg<T> get_CImg3dtoobject3d(CImgList<tp>& primitives, CImgList<tc>& colors, CImgList<to>& opacities) const {
+      char error_message[1024] = { 0 };
+      if (!is_CImg3d(true,error_message))
+        throw CImgInstanceException(_cimg_instance
+                                    "CImg3dtoobject3d() : image instance is not a CImg3d (%s).",
+                                    cimg_instance,error_message);
+      const T *ptrs = _data + 6;
+      const unsigned int
+        nb_points = cimg::float2uint((float)*(ptrs++)),
+        nb_primitives = cimg::float2uint((float)*(ptrs++));
+      const CImg<T> points = CImg<T>(ptrs,3,nb_points,1,1,true).get_transpose();
+      ptrs+=3*nb_points;
+      primitives.assign(nb_primitives);
+      cimglist_for(primitives,p) {
+        const unsigned int nb_inds = (unsigned int)*(ptrs++);
+        primitives[p].assign(1,nb_inds);
+        tp *ptrp = primitives[p]._data;
+        for (unsigned int i = 0; i<nb_inds; ++i) *(ptrp++) = (tp)cimg::float2uint((float)*(ptrs++));
+      }
+      colors.assign(nb_primitives);
+      cimglist_for(colors,c) {
+        if ((int)*ptrs==-128) {
+          ++ptrs;
+          const unsigned int w = (unsigned int)*(ptrs++), h = (unsigned int)*(ptrs++), s = (unsigned int)*(ptrs++);
+          if (!h && !s) colors[c].assign(colors[w],true);
+          else { colors[c].assign(ptrs,w,h,1,s,false); ptrs+=w*h*s; }
+        } else { colors[c].assign(ptrs,1,1,1,3,false); ptrs+=3; }
+      }
+      opacities.assign(nb_primitives);
+      cimglist_for(opacities,o) {
+        if ((int)*ptrs==-128) {
+          ++ptrs;
+          const unsigned int w = (unsigned int)*(ptrs++), h = (unsigned int)*(ptrs++), s = (unsigned int)*(ptrs++);
+          if (!h && !s) opacities[o].assign(opacities[w],true);
+          else { opacities[o].assign(ptrs,w,h,1,s,false); ptrs+=w*h*s; }
+        } else opacities[o].assign(1,1,1,1,*(ptrs++));
+      }
+      return points;
+    }
+
+    template<typename tp, typename tc, typename to>
+    CImg<T>& CImg3dtoobject3d(CImgList<tp>& primitives, CImgList<tc>& colors, CImgList<to>& opacities) {
+      return get_CImg3dtoobject3d(primitives,colors,opacities).move_to(*this);
+    }
+
+    //@}
+    //---------------------------
+    //
+    //! \name Drawing Functions
+    //@{
+    //---------------------------
+
+    // 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 whd = 0;
+      static const tc *col = 0;
+      if (init) {
+        nopacity = cimg::abs(opacity);
+        copacity = 1 - cimg::max(opacity,0);
+        whd = _width*_height*_depth;
+      } else {
+        const int nx0 = x0>0?x0:0, nx1 = x1<width()?x1:width()-1, dx = nx1 - nx0;
+        if (dx>=0) {
+          col = color;
+          const unsigned int off = whd-dx-1;
+          T *ptrd = data(nx0,y);
+          if (opacity>=1) { // ** Opaque drawing **
+            if (brightness==1) { // Brightness==1
+              if (sizeof(T)!=1) cimg_forC(*this,c) {
+                const T val = (T)*(col++);
+                for (int x = dx; x>=0; --x) *(ptrd++) = val;
+                ptrd+=off;
+              } else cimg_forC(*this,c) {
+                const T val = (T)*(col++);
+                std::memset(ptrd,(int)val,dx+1);
+                ptrd+=whd;
+              }
+            } else if (brightness<1) { // Brightness<1
+              if (sizeof(T)!=1) cimg_forC(*this,c) {
+                const T val = (T)(*(col++)*brightness);
+                for (int x = dx; x>=0; --x) *(ptrd++) = val;
+                ptrd+=off;
+              } else cimg_forC(*this,c) {
+                const T val = (T)(*(col++)*brightness);
+                std::memset(ptrd,(int)val,dx+1);
+                ptrd+=whd;
+              }
+            } else { // Brightness>1
+              if (sizeof(T)!=1) cimg_forC(*this,c) {
+                const T val = (T)((2-brightness)**(col++) + (brightness-1)*maxval);
+                for (int x = dx; x>=0; --x) *(ptrd++) = val;
+                ptrd+=off;
+              } else cimg_forC(*this,c) {
+                const T val = (T)((2-brightness)**(col++) + (brightness-1)*maxval);
+                std::memset(ptrd,(int)val,dx+1);
+                ptrd+=whd;
+              }
+            }
+          } else { // ** Transparent drawing **
+            if (brightness==1) { // Brightness==1
+              cimg_forC(*this,c) {
+                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_forC(*this,c) {
+                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_forC(*this,c) {
+                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 spectrum() 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 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_instance
+                                    "draw_point() : Specified color is (null).",
+                                    cimg_instance);
+      if (x0>=0 && y0>=0 && z0>=0 && x0<width() && y0<height() && z0<depth()) {
+        const unsigned int whd = _width*_height*_depth;
+        const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
+        T *ptrd = data(x0,y0,z0,0);
+        const tc *col = color;
+        if (opacity>=1) cimg_forC(*this,c) { *ptrd = (T)*(col++); ptrd+=whd; }
+        else cimg_forC(*this,c) { *ptrd = (T)(*(col++)*nopacity + *ptrd*copacity); ptrd+=whd; }
+      }
+      return *this;
+    }
+
+    // Draw a cloud of colored points.
+    template<typename t, typename tc>
+    CImg<T>& draw_point(const CImg<t>& points,
+                        const tc *const color, const float opacity=1) {
+      if (is_empty() || !points) return *this;
+      switch (points._height) {
+      case 0 : case 1 :
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_point() : Invalid specified point set (%u,%u,%u,%u,%p).",
+                                    cimg_instance,
+                                    points._width,points._height,points._depth,points._spectrum,points._data);
+      case 2 : {
+        cimg_forX(points,i) draw_point((int)points(i,0),(int)points(i,1),color,opacity);
+      } break;
+      default : {
+        cimg_forX(points,i) draw_point((int)points(i,0),(int)points(i,1),(int)points(i,2),color,opacity);
+      }
+      }
+      return *this;
+    }
+
+    //! 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 spectrum() 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_instance
+                                    "draw_line() : Specified color is (null).",
+                                    cimg_instance);
+      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>=width()) return *this;
+      if (xleft<0) { yleft-=(int)((float)xleft*((float)yright - yleft)/((float)xright - xleft)); xleft = 0; }
+      if (xright>=width()) { yright-=(int)(((float)xright - width())*((float)yright - yleft)/((float)xright - xleft)); xright = width() - 1; }
+      if (ydown<0 || yup>=height()) return *this;
+      if (yup<0) { xup-=(int)((float)yup*((float)xdown - xup)/((float)ydown - yup)); yup = 0; }
+      if (ydown>=height()) { xdown-=(int)(((float)ydown - height())*((float)xdown - xup)/((float)ydown - yup)); ydown = height() - 1; }
+      T *ptrd0 = data(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_forC(*this,c) { *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_forC(*this,c) { *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_forC(*this,c) { *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_forC(*this,c) { *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, with z-buffering.
+    template<typename tz,typename tc>
+    CImg<T>& draw_line(CImg<tz>& 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) {
+      typedef typename cimg::superset<tz,float>::type tzfloat;
+      if (is_empty() || z0<=0 || z1<=0) return *this;
+      if (!color)
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_line() : Specified color is (null).",
+                                    cimg_instance);
+      if (!is_sameXY(zbuffer))
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_line() : Instance and specified Z-buffer (%u,%u,%u,%u,%p) have different dimensions.",
+                                    cimg_instance,
+                                    zbuffer._width,zbuffer._height,zbuffer._depth,zbuffer._spectrum,zbuffer._data);
+      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;
+      tzfloat
+        Z0 = 1/(tzfloat)z0, Z1 = 1/(tzfloat)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>=width()) return *this;
+      if (xleft<0) {
+        const float D = (float)xright - xleft;
+        yleft-=(int)((float)xleft*((float)yright - yleft)/D);
+        zleft-=(tzfloat)xleft*(zright - zleft)/D;
+        xleft = 0;
+      }
+      if (xright>=width()) {
+        const float d = (float)xright - width(), D = (float)xright - xleft;
+        yright-=(int)(d*((float)yright - yleft)/D);
+        zright-=(tzfloat)d*(zright - zleft)/D;
+        xright = width() - 1;
+      }
+      if (ydown<0 || yup>=height()) return *this;
+      if (yup<0) {
+        const float D = (float)ydown - yup;
+        xup-=(int)((float)yup*((float)xdown - xup)/D);
+        zup-=(tzfloat)yup*(zdown - zup)/D;
+        yup = 0;
+      }
+      if (ydown>=height()) {
+        const float d = (float)ydown - height(), D = (float)ydown - yup;
+        xdown-=(int)(d*((float)xdown - xup)/D);
+        zdown-=(tzfloat)d*(zdown - zup)/D;
+        ydown = height() - 1;
+      }
+      T *ptrd0 = data(nx0,ny0);
+      tz *ptrz = zbuffer.data(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) {
+          const tzfloat z = Z0 + x*dz/ndx;
+          if (z>=(tzfloat)*ptrz && pattern&hatch) {
+            *ptrz = (tz)z;
+            T *ptrd = ptrd0; const tc *col = color;
+            cimg_forC(*this,c) { *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 tzfloat z = Z0 + x*dz/ndx;
+          if (z>=(tzfloat)*ptrz) {
+            *ptrz = (tz)z;
+            T *ptrd = ptrd0; const tc *col = color;
+            cimg_forC(*this,c) { *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 tzfloat z = Z0 + x*dz/ndx;
+          if (z>=(tzfloat)*ptrz && pattern&hatch) {
+            *ptrz = (tz)z;
+            T *ptrd = ptrd0; const tc *col = color;
+            cimg_forC(*this,c) { *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 tzfloat z = Z0 + x*dz/ndx;
+          if (z>=(tzfloat)*ptrz) {
+            *ptrz = (tz)z;
+            T *ptrd = ptrd0; const tc *col = color;
+            cimg_forC(*this,c) { *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 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_instance
+                                    "draw_line() : Specified color is (null).",
+                                    cimg_instance);
+      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>=width()) return *this;
+      if (nx0<0) { const float D = 1.0f + nx1 - nx0; ny0-=(int)((float)nx0*(1.0f + ny1 - ny0)/D); nz0-=(int)((float)nx0*(1.0f + nz1 - nz0)/D); nx0 = 0; }
+      if (nx1>=width()) { const float d = (float)nx1 - width(), D = 1.0f + nx1 - nx0; ny1+=(int)(d*(1.0f + ny0 - ny1)/D); nz1+=(int)(d*(1.0f + nz0 - nz1)/D); nx1 = width() - 1; }
+      if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,nz0,nz1);
+      if (ny1<0 || ny0>=height()) return *this;
+      if (ny0<0) { const float D = 1.0f + ny1 - ny0; nx0-=(int)((float)ny0*(1.0f + nx1 - nx0)/D); nz0-=(int)((float)ny0*(1.0f + nz1 - nz0)/D); ny0 = 0; }
+      if (ny1>=height()) { const float d = (float)ny1 - height(), D = 1.0f + ny1 - ny0; nx1+=(int)(d*(1.0f + nx0 - nx1)/D); nz1+=(int)(d*(1.0f + nz0 - nz1)/D); ny1 = height() - 1; }
+      if (nz0>nz1) cimg::swap(nx0,nx1,ny0,ny1,nz0,nz1);
+      if (nz1<0 || nz0>=depth()) return *this;
+      if (nz0<0) { const float D = 1.0f + nz1 - nz0; nx0-=(int)((float)nz0*(1.0f + nx1 - nx0)/D); ny0-=(int)((float)nz0*(1.0f + ny1 - ny0)/D); nz0 = 0; }
+      if (nz1>=depth()) { const float d = (float)nz1 - depth(), D = 1.0f + nz1 - nz0; nx1+=(int)(d*(1.0f + nx0 - nx1)/D); ny1+=(int)(d*(1.0f + ny0 - ny1)/D); nz1 = depth() - 1; }
+      const unsigned int dmax = cimg::max(cimg::abs(nx1 - nx0),cimg::abs(ny1 - ny0),nz1 - nz0), whd = _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 = data((unsigned int)x,(unsigned int)y,(unsigned int)z);
+          const tc *col = color; cimg_forC(*this,c) { *ptrd = (T)*(col++); ptrd+=whd; }
+        }
+        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 = data((unsigned int)x,(unsigned int)y,(unsigned int)z);
+            const tc *col = color; cimg_forC(*this,c) { *ptrd = (T)(*(col++)*nopacity + *ptrd*copacity); ptrd+=whd; }
+          }
+          x+=px; y+=py; z+=pz; if (pattern) { hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1); }
+        }
+      }
+      return *this;
+    }
+
+    //! 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._depth>1 || texture._spectrum<_spectrum)
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_line() : Invalid specified texture (%u,%u,%u,%u,%p).",
+                                    cimg_instance,
+                                    texture._width,texture._height,texture._depth,texture._spectrum,texture._data);
+      if (is_overlapped(texture)) return draw_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>=width()) return *this;
+      if (xleft<0) {
+        const float D = (float)xright - xleft;
+        yleft-=(int)((float)xleft*((float)yright - yleft)/D);
+        txleft-=(int)((float)xleft*((float)txright - txleft)/D);
+        tyleft-=(int)((float)xleft*((float)tyright - tyleft)/D);
+        xleft = 0;
+      }
+      if (xright>=width()) {
+        const float d = (float)xright - width(), D = (float)xright - xleft;
+        yright-=(int)(d*((float)yright - yleft)/D);
+        txright-=(int)(d*((float)txright - txleft)/D);
+        tyright-=(int)(d*((float)tyright - tyleft)/D);
+        xright = width() - 1;
+      }
+      if (ydown<0 || yup>=height()) return *this;
+      if (yup<0) {
+        const float D = (float)ydown - yup;
+        xup-=(int)((float)yup*((float)xdown - xup)/D);
+        txup-=(int)((float)yup*((float)txdown - txup)/D);
+        tyup-=(int)((float)yup*((float)tydown - tyup)/D);
+        yup = 0;
+      }
+      if (ydown>=height()) {
+        const float d = (float)ydown - height(), D = (float)ydown - yup;
+        xdown-=(int)(d*((float)xdown - xup)/D);
+        txdown-=(int)(d*((float)txdown - txup)/D);
+        tydown-=(int)(d*((float)tydown - tyup)/D);
+        ydown = height() - 1;
+      }
+      T *ptrd0 = data(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_forC(*this,c) { *ptrd = (T)texture(tx,ty,0,c); 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_forC(*this,c) { *ptrd = (T)texture(tx,ty,0,c); 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_forC(*this,c) { *ptrd = (T)(nopacity*texture(tx,ty,0,c) + *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_forC(*this,c) { *ptrd = (T)(nopacity*texture(tx,ty,0,c) + *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._depth>1 || texture._spectrum<_spectrum)
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_line() : Invalid specified texture (%u,%u,%u,%u,%p).",
+                                    cimg_instance,
+                                    texture._width,texture._height,texture._depth,texture._spectrum,texture._data);
+      if (is_overlapped(texture)) return draw_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>=width()) return *this;
+      if (xleft<0) {
+        const float D = (float)xright - xleft;
+        yleft-=(int)((float)xleft*((float)yright - yleft)/D);
+        zleft-=(float)xleft*(zright - zleft)/D;
+        txleft-=(float)xleft*(txright - txleft)/D;
+        tyleft-=(float)xleft*(tyright - tyleft)/D;
+        xleft = 0;
+      }
+      if (xright>=width()) {
+        const float d = (float)xright - width(), D = (float)xright - xleft;
+        yright-=(int)(d*((float)yright - yleft)/D);
+        zright-=d*(zright - zleft)/D;
+        txright-=d*(txright - txleft)/D;
+        tyright-=d*(tyright - tyleft)/D;
+        xright = width() - 1;
+      }
+      if (ydown<0 || yup>=height()) return *this;
+      if (yup<0) {
+        const float D = (float)ydown - yup;
+        xup-=(int)((float)yup*((float)xdown - xup)/D);
+        zup-=(float)yup*(zdown - zup)/D;
+        txup-=(float)yup*(txdown - txup)/D;
+        tyup-=(float)yup*(tydown - tyup)/D;
+        yup = 0;
+      }
+      if (ydown>=height()) {
+        const float d = (float)ydown - height(), D = (float)ydown - yup;
+        xdown-=(int)(d*((float)xdown - xup)/D);
+        zdown-=d*(zdown - zup)/D;
+        txdown-=d*(txdown - txup)/D;
+        tydown-=d*(tydown - tyup)/D;
+        ydown = height() - 1;
+      }
+      T *ptrd0 = data(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_forC(*this,c) { *ptrd = (T)texture((int)(tx/z),(int)(ty/z),0,c); 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_forC(*this,c) { *ptrd = (T)texture((int)(tx/z),(int)(ty/z),0,c); 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_forC(*this,c) { *ptrd = (T)(nopacity*texture((int)(tx/z),(int)(ty/z),0,c) + *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_forC(*this,c) { *ptrd = (T)(nopacity*texture((int)(tx/z),(int)(ty/z),0,c) + *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 tz, typename tc>
+    CImg<T>& draw_line(CImg<tz>& 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) {
+      typedef typename cimg::superset<tz,float>::type tzfloat;
+      if (is_empty() || z0<=0 || z1<=0) return *this;
+      if (!is_sameXY(zbuffer))
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_line() : Instance and specified Z-buffer (%u,%u,%u,%u,%p) have different dimensions.",
+                                    cimg_instance,
+                                    zbuffer._width,zbuffer._height,zbuffer._depth,zbuffer._spectrum,zbuffer._data);
+      if (texture._depth>1 || texture._spectrum<_spectrum)
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_line() : Invalid specified texture (%u,%u,%u,%u,%p).",
+                                    cimg_instance,
+                                    texture._width,texture._height,texture._depth,texture._spectrum,texture._data);
+      if (is_overlapped(texture)) return draw_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,
+        dtx = Tx1 - Tx0, dty = Ty1 - Ty0,
+        tnx0 = Tx0, tnx1 = Tx1, tny0 = Ty0, tny1 = Ty1,
+        &txleft = xdir?tnx0:tnx1, &tyleft = xdir?tny0:tny1,
+        &txright = xdir?tnx1:tnx0, &tyright = xdir?tny1:tny0,
+        &txup = ydir?tnx0:tnx1, &tyup = ydir?tny0:tny1,
+        &txdown = ydir?tnx1:tnx0, &tydown = ydir?tny1:tny0;
+      tzfloat
+        Z0 = 1/(tzfloat)z0, Z1 = 1/(tzfloat)z1,
+        dz = Z1 - Z0,  nz0 = Z0, nz1 = Z1,
+        &zleft = xdir?nz0:nz1,
+        &zright = xdir?nz1:nz0,
+        &zup = ydir?nz0:nz1,
+        &zdown = ydir?nz1:nz0;
+      if (xright<0 || xleft>=width()) return *this;
+      if (xleft<0) {
+        const float D = (float)xright - xleft;
+        yleft-=(int)((float)xleft*((float)yright - yleft)/D);
+        zleft-=(float)xleft*(zright - zleft)/D;
+        txleft-=(float)xleft*(txright - txleft)/D;
+        tyleft-=(float)xleft*(tyright - tyleft)/D;
+        xleft = 0;
+      }
+      if (xright>=width()) {
+        const float d = (float)xright - width(), D = (float)xright - xleft;
+        yright-=(int)(d*((float)yright - yleft)/D);
+        zright-=d*(zright - zleft)/D;
+        txright-=d*(txright - txleft)/D;
+        tyright-=d*(tyright - tyleft)/D;
+        xright = width()-1;
+      }
+      if (ydown<0 || yup>=height()) return *this;
+      if (yup<0) {
+        const float D = (float)ydown - yup;
+        xup-=(int)((float)yup*((float)xdown - xup)/D);
+        zup-=yup*(zdown - zup)/D;
+        txup-=yup*(txdown - txup)/D;
+        tyup-=yup*(tydown - tyup)/D;
+        yup = 0;
+      }
+      if (ydown>=height()) {
+        const float d = (float)ydown - height(), D = (float)ydown - yup;
+        xdown-=(int)(d*((float)xdown - xup)/D);
+        zdown-=d*(zdown - zup)/D;
+        txdown-=d*(txdown - txup)/D;
+        tydown-=d*(tydown - tyup)/D;
+        ydown = height()-1;
+      }
+      T *ptrd0 = data(nx0,ny0);
+      tz *ptrz = zbuffer.data(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 tzfloat z = Z0 + x*dz/ndx;
+            if (z>=(tzfloat)*ptrz) {
+              *ptrz = (tz)z;
+              const float tx = Tx0 + x*dtx/ndx, ty = Ty0 + x*dty/ndx;
+              T *ptrd = ptrd0; cimg_forC(*this,c) { *ptrd = (T)texture((int)(tx/z),(int)(ty/z),0,c); 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 tzfloat z = Z0 + x*dz/ndx;
+          if (z>=(tzfloat)*ptrz) {
+            *ptrz = (tz)z;
+            const float tx = Tx0 + x*dtx/ndx, ty = Ty0 + x*dty/ndx;
+            T *ptrd = ptrd0; cimg_forC(*this,c) { *ptrd = (T)texture((int)(tx/z),(int)(ty/z),0,c); 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 tzfloat z = Z0 + x*dz/ndx;
+            if (z>=(tzfloat)*ptrz) {
+              *ptrz = (tz)z;
+              const float tx = Tx0 + x*dtx/ndx, ty = Ty0 + x*dty/ndx;
+              T *ptrd = ptrd0; cimg_forC(*this,c) { *ptrd = (T)(nopacity*texture((int)(tx/z),(int)(ty/z),0,c) + *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 tzfloat z = Z0 + x*dz/ndx;
+          if (z>=(tzfloat)*ptrz) {
+            *ptrz = (tz)z;
+            const float tx = Tx0 + x*dtx/ndx, ty = Ty0 + x*dty/ndx;
+            T *ptrd = ptrd0; cimg_forC(*this,c) { *ptrd = (T)(nopacity*texture((int)(tx/z),(int)(ty/z),0,c) + *ptrd*copacity); ptrd+=wh; }
+          }
+          ptrd0+=offx; ptrz+=offx;
+          if ((error-=dy)<0) { ptrd0+=offy; ptrz+=offx; error+=dx; }
+        }
+      }
+      return *this;
+    }
+
+    //! Draw a set of consecutive colored lines in the image instance.
+    /**
+       \param points Coordinates of vertices, stored as a list of vectors.
+       \param color Pointer to \c spectrum() 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 CImg<t>& points,
+                       const tc *const color, const float opacity=1,
+                       const unsigned int pattern=~0U, const bool init_hatch=true) {
+      if (is_empty() || !points || points._width<2) return *this;
+      bool ninit_hatch = init_hatch;
+      switch (points._height) {
+      case 0 : case 1 :
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_line() : Invalid specified point set (%u,%u,%u,%u,%p).",
+                                    cimg_instance,
+                                    points._width,points._height,points._depth,points._spectrum,points._data);
+
+      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<points._width; ++i) {
+          const int x = (int)points(i,0), y = (int)points(i,1);
+          draw_line(ox,oy,x,y,color,opacity,pattern,ninit_hatch);
+          ninit_hatch = false;
+          ox = x; oy = y;
+        }
+      } 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<points._width; ++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 colored arrow in the image instance.
+    /**
+       \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 spectrum() 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::PI/180), ang = (sq>0)?(float)std::atan2(v,u):0.0f,
+        l = (length>=0)?length:-length*(float)std::sqrt(sq)/100;
+      if (sq>0) {
+        const float
+            cl = (float)std::cos(ang - deg), sl = (float)std::sin(ang - deg),
+            cr = (float)std::cos(ang + deg), sr = (float)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 cubic spline curve in the image instance.
+    /**
+       \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 spectrum() 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=0.25, const unsigned int pattern=~0U,
+                         const bool init_hatch=true) {
+      if (is_empty()) return *this;
+      if (!color)
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_spline() : Specified color is (null).",
+                                    cimg_instance);
+      if (x0==x1 && y0==y1) return draw_point(x0,y0,color,opacity);
+      bool ninit_hatch = init_hatch;
+      const float
+        ax = u0 + u1 + 2*(x0 - x1),
+        bx = 3*(x1 - x0) - 2*u0 - u1,
+        ay = v0 + v1 + 2*(y0 - y1),
+        by = 3*(y1 - y0) - 2*v0 - v1,
+        _precision = 1/(std::sqrt(cimg::sqr((float)x0-x1)+cimg::sqr((float)y0-y1))*(precision>0?precision:1));
+      int ox = x0, oy = y0;
+      for (float t = 0; t<1; t+=_precision) {
+        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 draw_line(ox,oy,x1,y1,color,opacity,pattern,false);
+    }
+
+    //! Draw a cubic spline curve in the image instance (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_instance
+                                    "draw_spline() : Specified color is (null).",
+                                    cimg_instance);
+      if (x0==x1 && y0==y1 && z0==z1) return draw_point(x0,y0,z0,color,opacity);
+      bool ninit_hatch = init_hatch;
+      const float
+        ax = u0 + u1 + 2*(x0 - x1),
+        bx = 3*(x1 - x0) - 2*u0 - u1,
+        ay = v0 + v1 + 2*(y0 - y1),
+        by = 3*(y1 - y0) - 2*v0 - v1,
+        az = w0 + w1 + 2*(z0 - z1),
+        bz = 3*(z1 - z0) - 2*w0 - w1,
+        _precision = 1/(std::sqrt(cimg::sqr(x0-x1)+cimg::sqr(y0-y1))*(precision>0?precision:1));
+      int ox = x0, oy = y0, oz = z0;
+      for (float t = 0; t<1; t+=_precision) {
+        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 draw_line(ox,oy,oz,x1,y1,z1,color,opacity,pattern,false);
+    }
+
+    //! Draw a cubic spline curve in the image instance.
+    /**
+       \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 (texture._depth>1 || texture._spectrum<_spectrum)
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_line() : Invalid specified texture (%u,%u,%u,%u,%p).",
+                                    cimg_instance,
+                                    texture._width,texture._height,texture._depth,texture._spectrum,texture._data);
+      if (is_empty()) return *this;
+      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);
+      if (x0==x1 && y0==y1) return draw_point(x0,y0,texture.get_vector_at(x0,y0),opacity);
+      bool ninit_hatch = init_hatch;
+      const float
+        ax = u0 + u1 + 2*(x0 - x1),
+        bx = 3*(x1 - x0) - 2*u0 - u1,
+        ay = v0 + v1 + 2*(y0 - y1),
+        by = 3*(y1 - y0) - 2*v0 - v1,
+        _precision = 1/(std::sqrt(cimg::sqr(x0-x1)+cimg::sqr(y0-y1))*(precision>0?precision:1));
+      int ox = x0, oy = y0, otx = tx0, oty = ty0;
+      for (float t1 = 0; t1<1; t1+=_precision) {
+        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),
+          nty = ty0 + (int)((ty1-ty0)*t1);
+        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 draw_line(ox,oy,x1,y1,texture,otx,oty,tx1,ty1,opacity,pattern,false);
+    }
+
+    // Draw a set of connected spline curves in the image instance (internal).
+    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) {
+      if (is_empty() || !points || !tangents || points._width<2 || tangents._width<2) return *this;
+      bool ninit_hatch = init_hatch;
+      switch (points._height) {
+      case 0 : case 1 :
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_spline() : Invalid specified point set (%u,%u,%u,%u,%p).",
+                                    cimg_instance,
+                                    points._width,points._height,points._depth,points._spectrum,points._data);
+
+      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<points._width; ++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<points._width; ++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 consecutive colored splines in the image instance.
+    template<typename tp, typename tc>
+    CImg<T>& draw_spline(const CImg<tp>& 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) {
+      if (is_empty() || !points || points._width<2) return *this;
+      CImg<Tfloat> tangents;
+      switch (points._height) {
+      case 0 : case 1 :
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_spline() : Invalid specified point set (%u,%u,%u,%u,%p).",
+                                    cimg_instance,
+                                    points._width,points._height,points._depth,points._spectrum,points._data);
+      case 2 : {
+        tangents.assign(points._width,points._height);
+        cimg_forX(points,p) {
+          const unsigned int
+            p0 = close_set?(p+points._width-1)%points._width:(p?p-1:0),
+            p1 = close_set?(p+1)%points._width:(p+1<points._width?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)std::sqrt(u0*u0 + v0*v0),
+            u1 = x1 - x,
+            v1 = y1 - y,
+            n1 = 1e-8f + (float)std::sqrt(u1*u1 + v1*v1),
+            u = u0/n0 + u1/n1,
+            v = v0/n0 + v1/n1,
+            n = 1e-8f + (float)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(points._width,points._height);
+        cimg_forX(points,p) {
+          const unsigned int
+            p0 = close_set?(p+points._width-1)%points._width:(p?p-1:0),
+            p1 = close_set?(p+1)%points._width:(p+1<points._width?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)std::sqrt(u0*u0 + v0*v0 + w0*w0),
+            u1 = x1 - x,
+            v1 = y1 - y,
+            w1 = z1 - z,
+            n1 = 1e-8f + (float)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)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,color,opacity,close_set,precision,pattern,init_hatch);
+    }
+
+    // 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<height() && 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_instance
+                                    "draw_triangle : Specified color is (null).",
+                                    cimg_instance);
+      _draw_triangle(x0,y0,x1,y1,x2,y2,color,opacity,1);
+      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 tc *const color, const float opacity,
+                           const unsigned int pattern) {
+      if (is_empty()) return *this;
+      if (!color)
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_triangle : Specified color is (null).",
+                                    cimg_instance);
+      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 filled colored triangle, with z-buffering.
+    template<typename tz, typename tc>
+    CImg<T>& draw_triangle(CImg<tz>& 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) {
+      typedef typename cimg::superset<tz,float>::type tzfloat;
+      if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this;
+      if (!color)
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_triangle() : Specified color is (null).",
+                                    cimg_instance);
+      if (!is_sameXY(zbuffer))
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_line() : Instance and specified Z-buffer (%u,%u,%u,%u,%p) have different dimensions.",
+                                    cimg_instance,
+                                    zbuffer._width,zbuffer._height,zbuffer._depth,zbuffer._spectrum,zbuffer._data);
+      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 whd = _width*_height*_depth, offx = _spectrum*whd;
+      int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2;
+      tzfloat nz0 = 1/(tzfloat)z0, nz1 = 1/(tzfloat)z1, nz2 = 1/(tzfloat)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>=height() || ny2<0) return *this;
+      tzfloat
+        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;
+        tzfloat zleft = zl, zright = zr;
+        if (xright<xleft) cimg::swap(xleft,xright,zleft,zright);
+        const int dx = xright - xleft;
+        const tzfloat pentez = (zright - zleft)/dx;
+        if (xleft<0 && dx) zleft-=xleft*(zright - zleft)/dx;
+        if (xleft<0) xleft = 0;
+        if (xright>=width()-1) xright = width() - 1;
+        T* ptrd = data(xleft,y,0,0);
+        tz *ptrz = zbuffer.data(xleft,y);
+        if (opacity>=1) {
+          if (nbrightness==1) for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) {
+              if (zleft>=(tzfloat)*ptrz) {
+                *ptrz = (tz)zleft;
+              const tc *col = color; cimg_forC(*this,c) { *ptrd = (T)*(col++); ptrd+=whd; }
+              ptrd-=offx;
+            }
+            zleft+=pentez;
+          } else if (nbrightness<1) for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) {
+              if (zleft>=(tzfloat)*ptrz) {
+                *ptrz = (tz)zleft;
+              const tc *col = color; cimg_forC(*this,c) { *ptrd = (T)(nbrightness*(*col++)); ptrd+=whd; }
+              ptrd-=offx;
+            }
+            zleft+=pentez;
+          } else for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) {
+              if (zleft>=(tzfloat)*ptrz) {
+                *ptrz = (tz)zleft;
+              const tc *col = color; cimg_forC(*this,c) { *ptrd = (T)((2-nbrightness)**(col++) + (nbrightness-1)*maxval); ptrd+=whd; }
+              ptrd-=offx;
+            }
+            zleft+=pentez;
+          }
+        } else {
+          if (nbrightness==1) for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) {
+              if (zleft>=(tzfloat)*ptrz) {
+                *ptrz = (tz)zleft;
+              const tc *col = color; cimg_forC(*this,c) { *ptrd = (T)(nopacity**(col++) + *ptrd*copacity); ptrd+=whd; }
+              ptrd-=offx;
+            }
+            zleft+=pentez;
+          } else if (nbrightness<1) for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) {
+              if (zleft>=(tzfloat)*ptrz) {
+                *ptrz = (tz)zleft;
+              const tc *col = color; cimg_forC(*this,c) { *ptrd = (T)(nopacity*nbrightness**(col++) + *ptrd*copacity); ptrd+=whd; }
+              ptrd-=offx;
+            }
+            zleft+=pentez;
+          } else for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) {
+              if (zleft>=(tzfloat)*ptrz) {
+                *ptrz = (tz)zleft;
+              const tc *col = color;
+              cimg_forC(*this,c) {
+                const T val = (T)((2-nbrightness)**(col++) + (nbrightness-1)*maxval);
+                *ptrd = (T)(nopacity*val + *ptrd*copacity);
+                ptrd+=whd;
+              }
+              ptrd-=offx;
+            }
+            zleft+=pentez;
+          }
+        }
+        zr+=pzr; zl+=pzl;
+      }
+      return *this;
+    }
+
+    //! Draw a 2d Gouraud-shaded colored triangle.
+    /**
+       \param x0 = X-coordinate of the first corner in the image instance.
+       \param y0 = Y-coordinate of the first corner in the image instance.
+       \param x1 = X-coordinate of the second corner in the image instance.
+       \param y1 = Y-coordinate of the second corner in the image instance.
+       \param x2 = X-coordinate of the third corner in the image instance.
+       \param y2 = Y-coordinate of the third corner in the image instance.
+       \param color = array of spectrum() 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_instance
+                                    "draw_triangle : Specified color is (null).",
+                                    cimg_instance);
+      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 whd = _width*_height*_depth, offx = _spectrum*whd-1;
+      int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2,
+        nc0 = (int)((brightness0<0.0f?0.0f:(brightness0>2.0f?2.0f:brightness0))*256.0f),
+        nc1 = (int)((brightness1<0.0f?0.0f:(brightness1>2.0f?2.0f:brightness1))*256.0f),
+        nc2 = (int)((brightness2<0.0f?0.0f:(brightness2>2.0f?2.0f:brightness2))*256.0f);
+      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>=height() || 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>=width()-1) xright = width() - 1;
+        T* ptrd = data(xleft,y);
+        if (opacity>=1) for (int x = xleft; x<=xright; ++x) {
+          const tc *col = color;
+          cimg_forC(*this,c) {
+            *ptrd = (T)(cleft<256?cleft**(col++)/256:((512-cleft)**(col++)+(cleft-256)*maxval)/256);
+            ptrd+=whd;
+          }
+          ptrd-=offx;
+          cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0);
+        } else for (int x = xleft; x<=xright; ++x) {
+          const tc *col = color;
+          cimg_forC(*this,c) {
+            const T val = (T)(cleft<256?cleft**(col++)/256:((512-cleft)**(col++)+(cleft-256)*maxval)/256);
+            *ptrd = (T)(nopacity*val + *ptrd*copacity);
+            ptrd+=whd;
+          }
+          ptrd-=offx;
+          cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0);
+        }
+      }
+      return *this;
+    }
+
+    //! Draw a 2d Gouraud-shaded colored triangle, with z-buffering.
+    template<typename tz, typename tc>
+    CImg<T>& draw_triangle(CImg<tz>& 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) {
+      typedef typename cimg::superset<tz,float>::type tzfloat;
+      if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this;
+      if (!color)
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_triangle() : Specified color is (null).",
+                                    cimg_instance);
+      if (!is_sameXY(zbuffer))
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_line() : Instance and specified Z-buffer (%u,%u,%u,%u,%p) have different dimensions.",
+                                    cimg_instance,
+                                    zbuffer._width,zbuffer._height,zbuffer._depth,zbuffer._spectrum,zbuffer._data);
+      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 whd = _width*_height*_depth, offx = _spectrum*whd;
+      int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2,
+        nc0 = (int)((brightness0<0.0f?0.0f:(brightness0>2.0f?2.0f:brightness0))*256.0f),
+        nc1 = (int)((brightness1<0.0f?0.0f:(brightness1>2.0f?2.0f:brightness1))*256.0f),
+        nc2 = (int)((brightness2<0.0f?0.0f:(brightness2>2.0f?2.0f:brightness2))*256.0f);
+      tzfloat nz0 = 1/(tzfloat)z0, nz1 = 1/(tzfloat)z1, nz2 = 1/(tzfloat)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>=height() || ny2<0) return *this;
+      tzfloat
+        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;
+        tzfloat 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 tzfloat 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>=width()-1) xright = width()-1;
+        T *ptrd = data(xleft,y);
+        tz *ptrz = zbuffer.data(xleft,y);
+        if (opacity>=1) for (int x = xleft; x<=xright; ++x, ++ptrd, ++ptrz) {
+            if (zleft>=(tzfloat)*ptrz) {
+              *ptrz = (tz)zleft;
+              const tc *col = color;
+              cimg_forC(*this,c) {
+                *ptrd = (T)(cleft<256?cleft**(col++)/256:((512-cleft)**(col++)+(cleft-256)*maxval)/256);
+                ptrd+=whd;
+              }
+              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>=(tzfloat)*ptrz) {
+              *ptrz = (tz)zleft;
+              const tc *col = color;
+              cimg_forC(*this,c) {
+                const T val = (T)(cleft<256?cleft**(col++)/256:((512-cleft)**(col++)+(cleft-256)*maxval)/256);
+                *ptrd = (T)(nopacity*val + *ptrd*copacity);
+                ptrd+=whd;
+              }
+              ptrd-=offx;
+            }
+            zleft+=pentez;
+            cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0);
+          }
+        zr+=pzr; zl+=pzl;
+      }
+      return *this;
+    }
+
+    //! Draw a colored triangle with interpolated colors.
+    template<typename tc1, typename tc2, typename tc3>
+    CImg<T>& draw_triangle(const int x0, const int y0,
+                           const int x1, const int y1,
+                           const int x2, const int y2,
+                           const tc1 *const color1,
+                           const tc2 *const color2,
+                           const tc3 *const color3,
+                           const float opacity=1) {
+      const unsigned char one = 1;
+      cimg_forC(*this,c) get_shared_channel(c).draw_triangle(x0,y0,x1,y1,x2,y2,&one,color1[c],color2[c],color3[c],opacity);
+      return *this;
+    }
+
+    //! Draw a 2d textured triangle.
+    /**
+       \param x0 = X-coordinate of the first corner in the image instance.
+       \param y0 = Y-coordinate of the first corner in the image instance.
+       \param x1 = X-coordinate of the second corner in the image instance.
+       \param y1 = Y-coordinate of the second corner in the image instance.
+       \param x2 = X-coordinate of the third corner in the image instance.
+       \param y2 = Y-coordinate of the third corner in the image instance.
+       \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._depth>1 || texture._spectrum<_spectrum)
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_triangle() : Invalid specified texture (%u,%u,%u,%u,%p).",
+                                    cimg_instance,
+                                    texture._width,texture._height,texture._depth,texture._spectrum,texture._data);
+      if (is_overlapped(texture)) return draw_triangle(x0,y0,x1,y1,x2,y2,+texture,tx0,ty0,tx1,ty1,tx2,ty2,opacity,brightness);
+      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 whd = _width*_height*_depth, twhd = texture._width*texture._height*texture._depth, offx = _spectrum*whd-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>=height() || 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>=width()-1) xright = width()-1;
+        T* ptrd = data(xleft,y,0,0);
+        if (opacity>=1) {
+          if (nbrightness==1) for (int x = xleft; x<=xright; ++x) {
+            const tc *col = texture.data(txleft,tyleft);
+            cimg_forC(*this,c) {
+              *ptrd = (T)*col;
+              ptrd+=whd; col+=twhd;
+            }
+            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.data(txleft,tyleft);
+            cimg_forC(*this,c) {
+              *ptrd = (T)(nbrightness**col);
+              ptrd+=whd; col+=twhd;
+            }
+            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.data(txleft,tyleft);
+            cimg_forC(*this,c) {
+              *ptrd = (T)((2-nbrightness)**(col++) + (nbrightness-1)*maxval);
+              ptrd+=whd; col+=twhd;
+            }
+            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.data(txleft,tyleft);
+            cimg_forC(*this,c) {
+              *ptrd = (T)(nopacity**col + *ptrd*copacity);
+              ptrd+=whd; col+=twhd;
+            }
+            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.data(txleft,tyleft);
+            cimg_forC(*this,c) {
+              *ptrd = (T)(nopacity*nbrightness**col + *ptrd*copacity);
+              ptrd+=whd; col+=twhd;
+            }
+            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.data(txleft,tyleft);
+            cimg_forC(*this,c) {
+              const T val = (T)((2-nbrightness)**(col++) + (nbrightness-1)*maxval);
+              *ptrd = (T)(nopacity*val + *ptrd*copacity);
+              ptrd+=whd; col+=twhd;
+            }
+            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._depth>1 || texture._spectrum<_spectrum)
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_triangle() : Invalid specified texture (%u,%u,%u,%u,%p).",
+                                    cimg_instance,
+                                    texture._width,texture._height,texture._depth,texture._spectrum,texture._data);
+      if (is_overlapped(texture)) return draw_triangle(x0,y0,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 whd = _width*_height*_depth, twhd = texture._width*texture._height*texture._depth, offx = _spectrum*whd-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>=height() || 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>=width()-1) xright = width()-1;
+        T* ptrd = data(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.data((int)(txleft*invz),(int)(tyleft*invz));
+            cimg_forC(*this,c) {
+              *ptrd = (T)*col;
+              ptrd+=whd; col+=twhd;
+            }
+            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.data((int)(txleft*invz),(int)(tyleft*invz));
+            cimg_forC(*this,c) {
+              *ptrd = (T)(nbrightness**col);
+              ptrd+=whd; col+=twhd;
+            }
+            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.data((int)(txleft*invz),(int)(tyleft*invz));
+            cimg_forC(*this,c) {
+              *ptrd = (T)((2-nbrightness)**col + (nbrightness-1)*maxval);
+              ptrd+=whd; col+=twhd;
+            }
+            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.data((int)(txleft*invz),(int)(tyleft*invz));
+            cimg_forC(*this,c) {
+              *ptrd = (T)(nopacity**col + *ptrd*copacity);
+              ptrd+=whd; col+=twhd;
+            }
+            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.data((int)(txleft*invz),(int)(tyleft*invz));
+            cimg_forC(*this,c) {
+              *ptrd = (T)(nopacity*nbrightness**col + *ptrd*copacity);
+              ptrd+=whd; col+=twhd;
+            }
+            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.data((int)(txleft*invz),(int)(tyleft*invz));
+            cimg_forC(*this,c) {
+              const T val = (T)((2-nbrightness)**col + (nbrightness-1)*maxval);
+              *ptrd = (T)(nopacity*val + *ptrd*copacity);
+              ptrd+=whd; col+=twhd;
+            }
+            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 tz, typename tc>
+    CImg<T>& draw_triangle(CImg<tz>& 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) {
+      typedef typename cimg::superset<tz,float>::type tzfloat;
+      if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this;
+      if (!is_sameXY(zbuffer))
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_line() : Instance and specified Z-buffer (%u,%u,%u,%u,%p) have different dimensions.",
+                                    cimg_instance,
+                                    zbuffer._width,zbuffer._height,zbuffer._depth,zbuffer._spectrum,zbuffer._data);
+
+      if (texture._depth>1 || texture._spectrum<_spectrum)
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_triangle() : Invalid specified texture (%u,%u,%u,%u,%p).",
+                                    cimg_instance,
+                                    texture._width,texture._height,texture._depth,texture._spectrum,texture._data);
+      if (is_overlapped(texture)) return draw_triangle(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 whd = _width*_height*_depth, twhd = texture._width*texture._height*texture._depth, offx = _spectrum*whd;
+      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;
+      tzfloat nz0 = 1/(tzfloat)z0, nz1 = 1/(tzfloat)z1, nz2 = 1/(tzfloat)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>=height() || 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),
+        txr = ny0>=0?ntx0:(ntx0 - ny0*(ntx2 - ntx0)/(ny2 - ny0)),
+        tyr = ny0>=0?nty0:(nty0 - ny0*(nty2 - nty0)/(ny2 - ny0)),
+        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)));
+      tzfloat
+        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; txl = ntx1; tyl = nty1; pzl = pzn; ptxl = ptxn; ptyl = ptyn; }
+        int xleft = xleft0, xright = xright0;
+        float txleft = txl, txright = txr, tyleft = tyl, tyright = tyr;
+        tzfloat zleft = zl, zright = zr;
+        if (xright<xleft) cimg::swap(xleft,xright,zleft,zright,txleft,txright,tyleft,tyright);
+        const int dx = xright - xleft;
+        const float pentetx = (txright - txleft)/dx, pentety = (tyright - tyleft)/dx;
+        const tzfloat pentez = (zright - zleft)/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>=width()-1) xright = width()-1;
+        T *ptrd = data(xleft,y,0,0);
+        tz *ptrz = zbuffer.data(xleft,y);
+        if (opacity>=1) {
+          if (nbrightness==1) for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) {
+              if (zleft>=(tzfloat)*ptrz) {
+                *ptrz = (tz)zleft;
+                const tzfloat invz = 1/zleft;
+                const tc *col = texture.data((int)(txleft*invz),(int)(tyleft*invz));
+                cimg_forC(*this,c) {
+                  *ptrd = (T)*col;
+                  ptrd+=whd; col+=twhd;
+                }
+                ptrd-=offx;
+              }
+              zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
+            } else if (nbrightness<1) for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) {
+              if (zleft>=(tzfloat)*ptrz) {
+                *ptrz = (tz)zleft;
+                const tzfloat invz = 1/zleft;
+                const tc *col = texture.data((int)(txleft*invz),(int)(tyleft*invz));
+                cimg_forC(*this,c) {
+                  *ptrd = (T)(nbrightness**col);
+                  ptrd+=whd; col+=twhd;
+                }
+                ptrd-=offx;
+              }
+              zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
+            } else for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) {
+              if (zleft>=(tzfloat)*ptrz) {
+                *ptrz = (tz)zleft;
+                const tzfloat invz = 1/zleft;
+                const tc *col = texture.data((int)(txleft*invz),(int)(tyleft*invz));
+                cimg_forC(*this,c) {
+                  *ptrd = (T)((2-nbrightness)**col + (nbrightness-1)*maxval);
+                  ptrd+=whd; col+=twhd;
+                }
+                ptrd-=offx;
+              }
+              zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
+            }
+        } else {
+          if (nbrightness==1) for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) {
+              if (zleft>=(tzfloat)*ptrz) {
+                *ptrz = (tz)zleft;
+                const tzfloat invz = 1/zleft;
+                const tc *col = texture.data((int)(txleft*invz),(int)(tyleft*invz));
+                cimg_forC(*this,c) {
+                  *ptrd = (T)(nopacity**col + *ptrd*copacity);
+                  ptrd+=whd; col+=twhd;
+                }
+                ptrd-=offx;
+              }
+              zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
+            } else if (nbrightness<1) for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) {
+              if (zleft>=(tzfloat)*ptrz) {
+                *ptrz = (tz)zleft;
+                const tzfloat invz = 1/zleft;
+                const tc *col = texture.data((int)(txleft*invz),(int)(tyleft*invz));
+                cimg_forC(*this,c) {
+                  *ptrd = (T)(nopacity*nbrightness**col + *ptrd*copacity);
+                  ptrd+=whd; col+=twhd;
+                }
+                ptrd-=offx;
+              }
+              zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
+            } else for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) {
+              if (zleft>=(tzfloat)*ptrz) {
+                *ptrz = (tz)zleft;
+                const tzfloat invz = 1/zleft;
+                const tc *col = texture.data((int)(txleft*invz),(int)(tyleft*invz));
+                cimg_forC(*this,c) {
+                  const T val = (T)((2-nbrightness)**col + (nbrightness-1)*maxval);
+                  *ptrd = (T)(nopacity*val + *ptrd*copacity);
+                  ptrd+=whd; col+=twhd;
+                }
+                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 image instance.
+       \param y0 = Y-coordinate of the first corner in the image instance.
+       \param x1 = X-coordinate of the second corner in the image instance.
+       \param y1 = Y-coordinate of the second corner in the image instance.
+       \param x2 = X-coordinate of the third corner in the image instance.
+       \param y2 = Y-coordinate of the third corner in the image instance.
+       \param color = array of spectrum() 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_instance
+                                    "draw_triangle : Specified color is (null).",
+                                    cimg_instance);
+      if (light._depth>1 || light._spectrum<_spectrum)
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_triangle() : Invalid specified light texture (%u,%u,%u,%u,%p).",
+                                    cimg_instance,light._width,light._height,light._depth,light._spectrum,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 whd = _width*_height*_depth, offx = _spectrum*whd-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>=height() || 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>=width()-1) xright = width()-1;
+        T* ptrd = data(xleft,y,0,0);
+        if (opacity>=1) for (int x = xleft; x<=xright; ++x) {
+          const tc *col = color;
+          cimg_forC(*this,c) {
+            const tl l = light(lxleft,lyleft,c);
+            *ptrd = (T)(l<1?l**(col++):((2-l)**(col++)+(l-1)*maxval));
+            ptrd+=whd;
+          }
+          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 tc *col = color;
+          cimg_forC(*this,c) {
+            const tl l = light(lxleft,lyleft,c);
+            const T val = (T)(l<1?l**(col++):((2-l)**(col++)+(l-1)*maxval));
+            *ptrd = (T)(nopacity*val + *ptrd*copacity);
+            ptrd+=whd;
+          }
+          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, with z-buffering.
+    template<typename tz, typename tc, typename tl>
+    CImg<T>& draw_triangle(CImg<tz>& 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) {
+      typedef typename cimg::superset<tz,float>::type tzfloat;
+      if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this;
+      if (!color)
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_triangle() : Specified color is (null).",
+                                    cimg_instance);
+      if (light._depth>1 || light._spectrum<_spectrum)
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_triangle() : Invalid specified light texture (%u,%u,%u,%u,%p).",
+                                    cimg_instance,light._width,light._height,light._depth,light._spectrum,light._data);
+      if (!is_sameXY(zbuffer))
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_line() : Instance and specified Z-buffer (%u,%u,%u,%u,%p) have different dimensions.",
+                                    cimg_instance,
+                                    zbuffer._width,zbuffer._height,zbuffer._depth,zbuffer._spectrum,zbuffer._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 whd = _width*_height*_depth, offx = _spectrum*whd;
+      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;
+      tzfloat nz0 = 1/(tzfloat)z0, nz1 = 1/(tzfloat)z1, nz2 = 1/(tzfloat)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>=height() || ny2<0) return *this;
+      tzfloat
+        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;
+        tzfloat 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 tzfloat 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>=width()-1) xright = width()-1;
+        T *ptrd = data(xleft,y,0,0);
+        tz *ptrz = zbuffer.data(xleft,y);
+        if (opacity>=1) for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) {
+            if (zleft>=(tzfloat)*ptrz) {
+              *ptrz = (tz)zleft;
+              const tc *col = color;
+              cimg_forC(*this,c) {
+                const tl l = light(lxleft,lyleft,c);
+                const tc cval = *(col++);
+                *ptrd = (T)(l<1?l*cval:(2-l)*cval+(l-1)*maxval);
+                ptrd+=whd;
+              }
+              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>=(tzfloat)*ptrz) {
+              *ptrz = (tz)zleft;
+              const tc *col = color;
+              cimg_forC(*this,c) {
+                const tl l = light(lxleft,lyleft,c);
+                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+=whd;
+              }
+              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 Gouraud-shaded textured triangle.
+    /**
+       \param x0 = X-coordinate of the first corner in the image instance.
+       \param y0 = Y-coordinate of the first corner in the image instance.
+       \param x1 = X-coordinate of the second corner in the image instance.
+       \param y1 = Y-coordinate of the second corner in the image instance.
+       \param x2 = X-coordinate of the third corner in the image instance.
+       \param y2 = Y-coordinate of the third corner in the image instance.
+       \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._depth>1 || texture._spectrum<_spectrum)
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_triangle() : Invalid specified texture (%u,%u,%u,%u,%p).",
+                                    cimg_instance,
+                                    texture._width,texture._height,texture._depth,texture._spectrum,texture._data);
+      if (is_overlapped(texture))
+        return draw_triangle(x0,y0,x1,y1,x2,y2,+texture,tx0,ty0,tx1,ty1,tx2,ty2,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 whd = _width*_height*_depth, twhd = texture._width*texture._height*texture._depth, offx = _spectrum*whd-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.0f?0.0f:(brightness0>2.0f?2.0f:brightness0))*256.0f),
+        nc1 = (int)((brightness1<0.0f?0.0f:(brightness1>2.0f?2.0f:brightness1))*256.0f),
+        nc2 = (int)((brightness2<0.0f?0.0f:(brightness2>2.0f?2.0f:brightness2))*256.0f);
+      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>=height() || 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>=width()-1) xright = width()-1;
+        T* ptrd = data(xleft,y,0,0);
+        if (opacity>=1) for (int x = xleft; x<=xright; ++x) {
+          const tc *col = texture.data(txleft,tyleft);
+          cimg_forC(*this,c) {
+            *ptrd = (T)(cleft<256?cleft**col/256:((512-cleft)**col+(cleft-256)*maxval)/256);
+            ptrd+=whd; col+=twhd;
+          }
+          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.data(txleft,tyleft);
+          cimg_forC(*this,c) {
+            const T val = (T)(cleft<256?cleft**col/256:((512-cleft)**col+(cleft-256)*maxval)/256);
+            *ptrd = (T)(nopacity*val + *ptrd*copacity);
+            ptrd+=whd; col+=twhd;
+          }
+          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._depth>1 || texture._spectrum<_spectrum)
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_triangle() : Invalid specified texture (%u,%u,%u,%u,%p).",
+                                    cimg_instance,
+                                    texture._width,texture._height,texture._depth,texture._spectrum,texture._data);
+      if (is_overlapped(texture)) return draw_triangle(x0,y0,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 whd = _width*_height*_depth, twhd = texture._width*texture._height*texture._depth, offx = _spectrum*whd-1;
+      int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2,
+        nc0 = (int)((brightness0<0.0f?0.0f:(brightness0>2.0f?2.0f:brightness0))*256.0f),
+        nc1 = (int)((brightness1<0.0f?0.0f:(brightness1>2.0f?2.0f:brightness1))*256.0f),
+        nc2 = (int)((brightness2<0.0f?0.0f:(brightness2>2.0f?2.0f:brightness2))*256.0f);
+      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>=height() || 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>=width()-1) xright = width()-1;
+        T* ptrd = data(xleft,y,0,0);
+        if (opacity>=1) for (int x = xleft; x<=xright; ++x) {
+          const float invz = 1/zleft;
+          const tc *col = texture.data((int)(txleft*invz),(int)(tyleft*invz));
+          cimg_forC(*this,c) {
+            *ptrd = (T)(cleft<256?cleft**col/256:((512-cleft)**col+(cleft-256)*maxval)/256);
+            ptrd+=whd; col+=twhd;
+          }
+          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.data((int)(txleft*invz),(int)(tyleft*invz));
+          cimg_forC(*this,c) {
+            const T val = (T)(cleft<256?cleft**col/256:((512-cleft)**col+(cleft-256)*maxval)/256);
+            *ptrd = (T)(nopacity*val + *ptrd*copacity);
+            ptrd+=whd; col+=twhd;
+          }
+          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 tz, typename tc>
+    CImg<T>& draw_triangle(CImg<tz>& 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) {
+      typedef typename cimg::superset<tz,float>::type tzfloat;
+      if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this;
+      if (!is_sameXY(zbuffer))
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_line() : Instance and specified Z-buffer (%u,%u,%u,%u,%p) have different dimensions.",
+                                    cimg_instance,
+                                    zbuffer._width,zbuffer._height,zbuffer._depth,zbuffer._spectrum,zbuffer._data);
+      if (texture._depth>1 || texture._spectrum<_spectrum)
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_triangle() : Invalid specified texture (%u,%u,%u,%u,%p).",
+                                    cimg_instance,
+                                    texture._width,texture._height,texture._depth,texture._spectrum,texture._data);
+      if (is_overlapped(texture)) return draw_triangle(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 whd = _width*_height*_depth, twhd = texture._width*texture._height*texture._depth, offx = _spectrum*whd;
+      int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2,
+        nc0 = (int)((brightness0<0.0f?0.0f:(brightness0>2.0f?2.0f:brightness0))*256.0f),
+        nc1 = (int)((brightness1<0.0f?0.0f:(brightness1>2.0f?2.0f:brightness1))*256.0f),
+        nc2 = (int)((brightness2<0.0f?0.0f:(brightness2>2.0f?2.0f:brightness2))*256.0f);
+      float
+        ntx0 = tx0/z0, nty0 = ty0/z0,
+        ntx1 = tx1/z1, nty1 = ty1/z1,
+        ntx2 = tx2/z2, nty2 = ty2/z2;
+      tzfloat nz0 = 1/(tzfloat)z0, nz1 = 1/(tzfloat)z1, nz2 = 1/(tzfloat)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>=height() || 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),
+        txr = ny0>=0?ntx0:(ntx0 - ny0*(ntx2 - ntx0)/(ny2 - ny0)),
+        tyr = ny0>=0?nty0:(nty0 - ny0*(nty2 - nty0)/(ny2 - ny0)),
+        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)));
+      tzfloat
+        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; txl = ntx1; tyl = nty1; pzl = pzn; ptxl = ptxn; ptyl = ptyn; }
+        int xleft = xleft0, xright = xright0, cleft = cleft0, cright = cright0;
+        float txleft = txl, txright = txr, tyleft = tyl, tyright = tyr;
+        tzfloat zleft = zl, zright = zr;
+        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);
+        float pentetx = (txright - txleft)/dx, pentety = (tyright - tyleft)/dx;
+        const tzfloat pentez = (zright - zleft)/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>=width()-1) xright = width()-1;
+        T* ptrd = data(xleft,y);
+        tz *ptrz = zbuffer.data(xleft,y);
+        if (opacity>=1) for (int x = xleft; x<=xright; ++x, ++ptrd, ++ptrz) {
+            if (zleft>=(tzfloat)*ptrz) {
+              *ptrz = (tz)zleft;
+              const tzfloat invz = 1/zleft;
+              const tc *col = texture.data((int)(txleft*invz),(int)(tyleft*invz));
+              cimg_forC(*this,c) {
+                *ptrd = (T)(cleft<256?cleft**col/256:((512-cleft)**col+(cleft-256)*maxval)/256);
+                ptrd+=whd; col+=twhd;
+              }
+              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>=(tzfloat)*ptrz) {
+              *ptrz = (tz)zleft;
+              const tzfloat invz = 1/zleft;
+              const tc *col = texture.data((int)(txleft*invz),(int)(tyleft*invz));
+              cimg_forC(*this,c) {
+                const T val = (T)(cleft<256?cleft**col/256:((512-cleft)**col+(cleft-256)*maxval)/256);
+                *ptrd = (T)(nopacity*val + *ptrd*copacity);
+                ptrd+=whd; col+=twhd;
+              }
+              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 image instance.
+       \param y0 = Y-coordinate of the first corner in the image instance.
+       \param x1 = X-coordinate of the second corner in the image instance.
+       \param y1 = Y-coordinate of the second corner in the image instance.
+       \param x2 = X-coordinate of the third corner in the image instance.
+       \param y2 = Y-coordinate of the third corner in the image instance.
+       \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._depth>1 || texture._spectrum<_spectrum)
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_triangle() : Invalid specified texture (%u,%u,%u,%u,%p).",
+                                    cimg_instance,
+                                    texture._width,texture._height,texture._depth,texture._spectrum,texture._data);
+      if (light._depth>1 || light._spectrum<_spectrum)
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_triangle() : Invalid specified light texture (%u,%u,%u,%u,%p).",
+                                    cimg_instance,light._width,light._height,light._depth,light._spectrum,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 whd = _width*_height*_depth, twhd = texture._width*texture._height*texture._depth, offx = _spectrum*whd-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>=height() || 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>=width()-1) xright = width()-1;
+        T* ptrd = data(xleft,y,0,0);
+        if (opacity>=1) for (int x = xleft; x<=xright; ++x) {
+          const tc *col = texture.data(txleft,tyleft);
+          cimg_forC(*this,c) {
+            const tl l = light(lxleft,lyleft,c);
+            *ptrd = (T)(l<1?l**col:(2-l)**col+(l-1)*maxval);
+            ptrd+=whd; col+=twhd;
+          }
+          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 tc *col = texture.data(txleft,tyleft);
+          cimg_forC(*this,c) {
+            const tl l = light(lxleft,lyleft,c);
+            const T val = (T)(l<1?l**col:(2-l)**col+(l-1)*maxval);
+            *ptrd = (T)(nopacity*val + *ptrd*copacity);
+            ptrd+=whd; col+=twhd;
+          }
+          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._depth>1 || texture._spectrum<_spectrum)
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_triangle() : Invalid specified texture (%u,%u,%u,%u,%p).",
+                                    cimg_instance,
+                                    texture._width,texture._height,texture._depth,texture._spectrum,texture._data);
+      if (light._depth>1 || light._spectrum<_spectrum)
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_triangle() : Invalid specified light texture (%u,%u,%u,%u,%p).",
+                                    cimg_instance,light._width,light._height,light._depth,light._spectrum,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 whd = _width*_height*_depth, twhd = texture._width*texture._height*texture._depth, offx = _spectrum*whd-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>=height() || 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>=width()-1) xright = width()-1;
+        T* ptrd = data(xleft,y,0,0);
+        if (opacity>=1) for (int x = xleft; x<=xright; ++x) {
+          const float invz = 1/zleft;
+          const tc *col = texture.data((int)(txleft*invz),(int)(tyleft*invz));
+          cimg_forC(*this,c) {
+            const tl l = light(lxleft,lyleft,c);
+            *ptrd = (T)(l<1?l**col:(2-l)**col+(l-1)*maxval);
+            ptrd+=whd; col+=twhd;
+          }
+          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 tc *col = texture.data((int)(txleft*invz),(int)(tyleft*invz));
+          cimg_forC(*this,c) {
+            const tl l = light(lxleft,lyleft,c);
+            const T val = (T)(l<1?l**col:(2-l)**col+(l-1)*maxval);
+            *ptrd = (T)(nopacity*val + *ptrd*copacity);
+            ptrd+=whd; col+=twhd;
+          }
+          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 tz, typename tc, typename tl>
+    CImg<T>& draw_triangle(CImg<tz>& 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) {
+      typedef typename cimg::superset<tz,float>::type tzfloat;
+      if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this;
+      if (!is_sameXY(zbuffer))
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_triangle() : Instance and specified Z-buffer (%u,%u,%u,%u,%p) have different dimensions.",
+                                    cimg_instance,
+                                    zbuffer._width,zbuffer._height,zbuffer._depth,zbuffer._spectrum,zbuffer._data);
+      if (texture._depth>1 || texture._spectrum<_spectrum)
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_triangle() : Invalid specified texture (%u,%u,%u,%u,%p).",
+                                    cimg_instance,
+                                    texture._width,texture._height,texture._depth,texture._spectrum,texture._data);
+      if (light._depth>1 || light._spectrum<_spectrum)
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_triangle() : Invalid specified light texture (%u,%u,%u,%u,%p).",
+                                    cimg_instance,light._width,light._height,light._depth,light._spectrum,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 whd = _width*_height*_depth, twhd = texture._width*texture._height*texture._depth, offx = _spectrum*whd;
+      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;
+      tzfloat nz0 = 1/(tzfloat)z0, nz1 = 1/(tzfloat)z1, nz2 = 1/(tzfloat)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>=height() || 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),
+        txr = ny0>=0?ntx0:(ntx0 - ny0*(ntx2 - ntx0)/(ny2 - ny0)),
+        tyr = ny0>=0?nty0:(nty0 - ny0*(nty2 - nty0)/(ny2 - ny0)),
+        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)));
+      tzfloat
+        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; txl = ntx1; tyl = nty1; pzl = pzn; ptxl = ptxn; ptyl = ptyn; }
+        int
+          xleft = xleft0, xright = xright0,
+          lxleft = lxleft0, lxright = lxright0,
+          lyleft = lyleft0, lyright = lyright0;
+        float txleft = txl, txright = txr, tyleft = tyl, tyright = tyr;
+        tzfloat zleft = zl, zright = zr;
+        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);
+        float pentetx = (txright - txleft)/dx, pentety = (tyright - tyleft)/dx;
+        const tzfloat 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;
+          txleft-=xleft*(txright - txleft)/dx;
+          tyleft-=xleft*(tyright - tyleft)/dx;
+        }
+        if (xleft<0) xleft = 0;
+        if (xright>=width()-1) xright = width()-1;
+        T* ptrd = data(xleft,y);
+        tz *ptrz = zbuffer.data(xleft,y);
+        if (opacity>=1) for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) {
+            if (zleft>=(tzfloat)*ptrz) {
+              *ptrz = (tz)zleft;
+              const tzfloat invz = 1/zleft;
+              const tc *col = texture.data((int)(txleft*invz),(int)(tyleft*invz));
+              cimg_forC(*this,c) {
+                const tl l = light(lxleft,lyleft,c);
+                *ptrd = (T)(l<1?l**col:(2-l)**col+(l-1)*maxval);
+                ptrd+=whd; col+=twhd;
+              }
+              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>=(tzfloat)*ptrz) {
+              *ptrz = (tz)zleft;
+              const tzfloat invz = 1/zleft;
+              const tc *col = texture.data((int)(txleft*invz),(int)(tyleft*invz));
+              cimg_forC(*this,c) {
+                const tl l = light(lxleft,lyleft,c);
+                const T val = (T)(l<1?l**col:(2-l)**col+(l-1)*maxval);
+                *ptrd = (T)(nopacity*val + *ptrd*copacity);
+                ptrd+=whd; col+=twhd;
+              }
+              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 4d filled rectangle in the image instance, at coordinates (\c x0,\c y0,\c z0,\c c0)-(\c x1,\c y1,\c z1,\c c1).
+    /**
+       \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 c0 C-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 c1 C-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 c0,
+                            const int x1, const int y1, const int z1, const int c1,
+                            const T val, const float opacity=1) {
+      if (is_empty()) return *this;
+      const bool bx = (x0<x1), by = (y0<y1), bz = (z0<z1), bc = (c0<c1);
+      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,
+        nc0 = bc?c0:c1, nc1 = bc?c1:c0;
+      const int
+        lX = (1 + nx1 - nx0) + (nx1>=width()?width() - 1 - nx1:0) + (nx0<0?nx0:0),
+        lY = (1 + ny1 - ny0) + (ny1>=height()?height() - 1 - ny1:0) + (ny0<0?ny0:0),
+        lZ = (1 + nz1 - nz0) + (nz1>=depth()?depth() - 1 - nz1:0) + (nz0<0?nz0:0),
+        lC = (1 + nc1 - nc0) + (nc1>=spectrum()?spectrum() - 1 - nc1:0) + (nc0<0?nc0: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 = data(nx0<0?0:nx0,ny0<0?0:ny0,nz0<0?0:nz0,nc0<0?0:nc0);
+      if (lX>0 && lY>0 && lZ>0 && lC>0)
+        for (int v = 0; v<lC; ++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 { 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 image instance, 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 spectrum() 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 (is_empty()) return *this;
+      if (!color)
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_rectangle : Specified color is (null).",
+                                    cimg_instance);
+      cimg_forC(*this,c) draw_rectangle(x0,y0,z0,c,x1,y1,z1,c,color[c],opacity);
+      return *this;
+    }
+
+    //! Draw a 3d outlined colored rectangle in the image instance.
+    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 2d filled colored rectangle in the image instance, 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 spectrum() 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 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 filled polygon in the image instance.
+    template<typename t, typename tc>
+    CImg<T>& draw_polygon(const CImg<t>& points,
+                          const tc *const color, const float opacity=1) {
+      if (is_empty() || !points || points._width<3) return *this;
+      if (!color)
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_polygon() : Specified color is (null).",
+                                    cimg_instance);
+
+      // Normalize 2d input coordinates.
+      CImg<intT> npoints(points._width,2);
+      int x = npoints(0,0) = (int)points(0,0), y = npoints(0,1) = (int)points(0,1);
+      unsigned int nb_points = 1;
+      for (unsigned int p = 1; p<points._width; ++p) {
+        const int nx = (int)points(p,0), ny = (int)points(p,1);
+        if (nx!=x || ny!=y) { npoints(nb_points,0) = nx; npoints(nb_points++,1) = ny; x = nx; y = ny; }
+      }
+
+      if (nb_points==3) return draw_triangle((int)npoints(0,0),(int)npoints(0,1),
+                                             (int)npoints(1,0),(int)npoints(1,1),
+                                             (int)npoints(2,0),(int)npoints(2,1),color,opacity);
+      // Draw polygon segments.
+      _draw_scanline(color,opacity);
+      int
+        xmax = 0, xmin = (int)npoints.get_shared_points(0,nb_points-1,0).min_max(xmax),
+        ymax = 0, ymin = (int)npoints.get_shared_points(0,nb_points-1,1).min_max(ymax);
+      if (xmax<0 || xmin>=width() || ymax<0 || ymin>=height()) return *this;
+      if (ymin==ymax) return _draw_scanline(xmin,xmax,ymin,color,opacity);
+      const unsigned int
+        nymin = ymin<0?0:(unsigned int)ymin,
+        nymax = ymax>=height()?_height-1:(unsigned int)ymax,
+        dy = 1 + nymax - nymin;
+      CImg<intT> X(1+2*nb_points,dy,1,1,0), tmp;
+      int cx = (int)npoints(0,0), cy = (int)npoints(0,1);
+      unsigned int cp = 0;
+      for (unsigned int p = 0; p<nb_points; ++p) {
+        const unsigned int np = (p!=nb_points-1)?p+1:0, ap = (np!=nb_points-1)?np+1:0;
+        const int
+          nx = (int)npoints(np,0), ny = (int)npoints(np,1), ay = (int)npoints(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:nb_points-1), py = (int)npoints(pp,1);
+          if (y0>=0 && y0<(int)dy && (!p || (cy>py && ay>cy) || (cy<py && ay<cy))) X(++X(0,y0),y0) = nx;
+          if (cy!=ay) { cp = np; cx = nx; cy = ny; }
+        }
+      }
+
+      // Draw polygon scanlines.
+      for (int y = 0; y<(int)dy; ++y) {
+        tmp.assign(X.data(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 outlined polygon in the image instance.
+    template<typename t, typename tc>
+    CImg<T>& draw_polygon(const CImg<t>& points,
+                          const tc *const color, const float opacity, const unsigned int pattern) {
+      if (is_empty() || !points || points._width<3) return *this;
+      bool ninit_hatch = true;
+      switch (points._height) {
+      case 0 : case 1 :
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_polygon() : Invalid specified point set.",
+                                    cimg_instance);
+      case 2 : { // 2d version.
+        CImg<intT> npoints(points._width,2);
+        int x = npoints(0,0) = (int)points(0,0), y = npoints(0,1) = (int)points(0,1);
+        unsigned int nb_points = 1;
+        for (unsigned int p = 1; p<points._width; ++p) {
+          const int nx = (int)points(p,0), ny = (int)points(p,1);
+          if (nx!=x || ny!=y) { npoints(nb_points,0) = nx; npoints(nb_points++,1) = ny; x = nx; y = ny; }
+        }
+        const int x0 = (int)npoints(0,0), y0 = (int)npoints(0,1);
+        int ox = x0, oy = y0;
+        for (unsigned int i = 1; i<nb_points; ++i) {
+          const int x = (int)npoints(i,0), y = (int)npoints(i,1);
+          draw_line(ox,oy,x,y,color,opacity,pattern,ninit_hatch);
+          ninit_hatch = false;
+          ox = x; oy = y;
+        }
+        draw_line(ox,oy,x0,y0,color,opacity,pattern,false);
+      } break;
+      default : { // 3d version.
+        CImg<intT> npoints(points._width,3);
+        int x = npoints(0,0) = (int)points(0,0), y = npoints(0,1) = (int)points(0,1), z = npoints(0,2) = (int)points(0,2);
+        unsigned int nb_points = 1;
+        for (unsigned int p = 1; p<points._width; ++p) {
+          const int nx = (int)points(p,0), ny = (int)points(p,1), nz = (int)points(p,2);
+          if (nx!=x || ny!=y || nz!=z) { npoints(nb_points,0) = nx; npoints(nb_points,1) = ny; npoints(nb_points++,2) = nz; x = nx; y = ny; z = nz; }
+        }
+        const int x0 = (int)npoints(0,0), y0 = (int)npoints(0,1), z0 = (int)npoints(0,2);
+        int ox = x0, oy = y0, oz = z0;
+        for (unsigned int i = 1; i<nb_points; ++i) {
+          const int x = (int)npoints(i,0), y = (int)npoints(i,1), z = (int)npoints(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 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 spectrum() 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()) return *this;
+      if (!color)
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_circle : Specified color is (null).",
+                                    cimg_instance);
+      _draw_scanline(color,opacity);
+      if (radius<0 || x0-radius>=width() || y0+radius<0 || y0-radius>=height()) return *this;
+      if (y0>=0 && y0<height()) _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<height()) _draw_scanline(x1,x2,y1,color,opacity);
+          if (y2>=0 && y2<height()) _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<height()) _draw_scanline(x1,x2,y1,color,opacity);
+          if (y2>=0 && y2<height()) _draw_scanline(x1,x2,y2,color,opacity);
+        }
+      }
+      return *this;
+    }
+
+    //! 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 spectrum() 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()) return *this;
+      if (!color)
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_circle : Specified color is (null).",
+                                    cimg_instance);
+      if (radius<0 || x0-radius>=width() || y0+radius<0 || y0-radius>=height()) 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 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 angle,
+                           const tc *const color, const float opacity,
+                           const unsigned int pattern) {
+      if (is_empty()) return *this;
+      if (!color)
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_ellipse : Specified color is (null).",
+                                    cimg_instance);
+      if (r1<=0 || r2<=0) return draw_point(x0,y0,color,opacity);
+      _draw_scanline(color,opacity);
+      const float
+        nr1 = cimg::abs(r1), nr2 = cimg::abs(r2),
+        nangle = (float)(angle*cimg::PI/180),
+        u = (float)std::cos(nangle),
+        v = (float)std::sin(nangle),
+        rmax = cimg::max(nr1,nr2),
+        l1 = (float)std::pow(rmax/(nr1>0?nr1:1e-6),2),
+        l2 = (float)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)std::sqrt(a*rmax*rmax/(a*c - b*b)),
+        tymin = y0 - yb - 1,
+        tymax = y0 + yb + 1,
+        ymin = tymin<0?0:tymin,
+        ymax = tymax>=height()?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)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 angle = Angle of the first radius.
+       \param color = array of spectrum() 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 angle,
+                          const tc *const color, const float opacity=1) {
+      return _draw_ellipse(x0,y0,r1,r2,angle,color,opacity,0U);
+    }
+
+    //! 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 spectrum() 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,std::sqrt(val(0)),std::sqrt(val(1)),
+                          std::atan2(vec(0,1),vec(0,0))*180/cimg::PI,
+                          color,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 spectrum() 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 angle,
+                          const tc *const color, const float opacity, const unsigned int pattern) {
+      if (pattern) _draw_ellipse(x0,y0,r1,r2,angle,color,opacity,pattern);
+      return *this;
+    }
+
+    //! 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 spectrum() 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,
+                          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,std::sqrt(val(0)),std::sqrt(val(1)),
+                          std::atan2(vec(0,1),vec(0,0))*180/cimg::PI,
+                          color,opacity,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 c0 C-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 c0,
+                        const CImg<t>& sprite, const float opacity=1) {
+      if (is_empty() || !sprite) return *this;
+      if (is_overlapped(sprite)) return draw_image(x0,y0,z0,c0,+sprite,opacity);
+      if (x0==0 && y0==0 && z0==0 && c0==0 && is_sameXYZC(sprite) && opacity>=1) return assign(sprite,false);
+      const bool bx = (x0<0), by = (y0<0), bz = (z0<0), bc = (c0<0);
+      const int
+        lX = sprite.width() - (x0 + sprite.width()>width()?x0 + sprite.width() - width():0) + (bx?x0:0),
+        lY = sprite.height() - (y0 + sprite.height()>height()?y0 + sprite.height() - height():0) + (by?y0:0),
+        lZ = sprite.depth() - (z0 + sprite.depth()>depth()?z0 + sprite.depth() - depth():0) + (bz?z0:0),
+        lC = sprite.spectrum() - (c0 + sprite.spectrum()>spectrum()?c0 + sprite.spectrum() - spectrum():0) + (bc?c0:0);
+      const t
+        *ptrs = sprite._data -
+        (bx?x0:0) -
+        (by?y0*sprite.width():0) -
+        (bz?z0*sprite.width()*sprite.height():0) -
+        (bc?c0*sprite.width()*sprite.height()*sprite.depth():0);
+      const unsigned long
+        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 && lC>0) {
+        T *ptrd = data(x0<0?0:x0,y0<0?0:y0,z0<0?0:z0,c0<0?0:c0);
+        for (int v = 0; v<lC; ++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;
+    }
+
+    // Optimized version (internal).
+    CImg<T>& draw_image(const int x0, const int y0, const int z0, const int c0,
+                        const CImg<T>& sprite, const float opacity=1) {
+      if (is_empty() || !sprite) return *this;
+      if (is_overlapped(sprite)) return draw_image(x0,y0,z0,c0,+sprite,opacity);
+      if (x0==0 && y0==0 && z0==0 && c0==0 && is_sameXYZC(sprite) && opacity>=1) return assign(sprite,false);
+      const bool bx = (x0<0), by = (y0<0), bz = (z0<0), bc = (c0<0);
+      const int
+        lX = sprite.width() - (x0 + sprite.width()>width()?x0 + sprite.width() - width():0) + (bx?x0:0),
+        lY = sprite.height() - (y0 + sprite.height()>height()?y0 + sprite.height() - height():0) + (by?y0:0),
+        lZ = sprite.depth() - (z0 + sprite.depth()>depth()?z0 + sprite.depth() - depth():0) + (bz?z0:0),
+        lC = sprite.spectrum() - (c0 + sprite.spectrum()>spectrum()?c0 + sprite.spectrum() - spectrum():0) + (bc?c0:0);
+      const T
+        *ptrs = sprite._data -
+        (bx?x0:0) -
+        (by?y0*sprite.width():0) -
+        (bz?z0*sprite.width()*sprite.height():0) -
+        (bc?c0*sprite.width()*sprite.height()*sprite.depth():0);
+      const unsigned long
+        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 && lC>0) {
+        T *ptrd = data(x0<0?0:x0,y0<0?0:y0,z0<0?0:z0,c0<0?0:c0);
+        for (int v = 0; v<lC; ++v) {
+          for (int z = 0; z<lZ; ++z) {
+            if (opacity>=1) for (int y = 0; y<lY; ++y) { 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;
+    }
+
+    //! 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 image instance (masked version).
+    /**
+       \param sprite Sprite image.
+       \param mask Mask image.
+       \param x0 X-coordinate of the sprite position in the image instance.
+       \param y0 Y-coordinate of the sprite position in the image instance.
+       \param z0 Z-coordinate of the sprite position in the image instance.
+       \param c0 C-coordinate of the sprite position in the image instance.
+       \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 c0,
+                        const CImg<ti>& sprite, const CImg<tm>& mask, const float opacity=1,
+                        const float mask_valmax=1) {
+      if (is_empty() || !sprite || !mask) return *this;
+      if (is_overlapped(sprite)) return draw_image(x0,y0,z0,c0,+sprite,mask,opacity,mask_valmax);
+      if (is_overlapped(mask))   return draw_image(x0,y0,z0,c0,sprite,+mask,opacity,mask_valmax);
+      if (mask._width!=sprite._width || mask._height!=sprite._height || mask._depth!=sprite._depth)
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_image() : Sprite (%u,%u,%u,%u,%p) and mask (%u,%u,%u,%u,%p) have incompatible dimensions.",
+                                    cimg_instance,
+                                    sprite._width,sprite._height,sprite._depth,sprite._spectrum,sprite._data,
+                                    mask._width,mask._height,mask._depth,mask._spectrum,mask._data);
+
+      const bool bx = (x0<0), by = (y0<0), bz = (z0<0), bc = (c0<0);
+      const int
+        lX = sprite.width() - (x0 + sprite.width()>width()?x0 + sprite.width() - width():0) + (bx?x0:0),
+        lY = sprite.height() - (y0 + sprite.height()>height()?y0 + sprite.height() - height():0) + (by?y0:0),
+        lZ = sprite.depth() - (z0 + sprite.depth()>depth()?z0 + sprite.depth() - depth():0) + (bz?z0:0),
+        lC = sprite.spectrum() - (c0 + sprite.spectrum()>spectrum()?c0 + sprite.spectrum() - spectrum():0) + (bc?c0:0);
+      const int
+        coff = -(bx?x0:0)-(by?y0*mask.width():0)-(bz?z0*mask.width()*mask.height():0)-(bc?c0*mask.width()*mask.height()*mask.depth():0),
+        ssize = mask.width()*mask.height()*mask.depth();
+      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 && lC>0) {
+        T *ptrd = data(x0<0?0:x0,y0<0?0:y0,z0<0?0:z0,c0<0?0:c0);
+        for (int c = 0; c<lC; ++c) {
+          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 text.
+    /**
+       \param x0 X-coordinate of the text in the image instance.
+       \param y0 Y-coordinate of the text in the image instance.
+       \param foreground_color Array of spectrum() values of type \c T, defining the foreground color (0 means 'transparent').
+       \param background_color Array of spectrum() 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, ...) {
+      if (!font) return *this;
+      char tmp[2048] = { 0 }; std::va_list ap; va_start(ap,font);
+      cimg_vsnprintf(tmp,sizeof(tmp),text,ap); va_end(ap);
+      return _draw_text(x0,y0,tmp,foreground_color,background_color,opacity,font);
+    }
+
+    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,
+                       const float opacity, const CImgList<t>& font, ...) {
+      if (!font) return *this;
+      char tmp[2048] = { 0 }; std::va_list ap; va_start(ap,font);
+      cimg_vsnprintf(tmp,sizeof(tmp),text,ap); va_end(ap);
+      return _draw_text(x0,y0,tmp,foreground_color,(tc*)0,opacity,font);
+    }
+
+    template<typename tc, typename t>
+    CImg<T>& draw_text(const int x0, const int y0,
+                       const char *const text,
+                       const int, const tc *const background_color,
+                       const float opacity, const CImgList<t>& font, ...) {
+      if (!font) return *this;
+      char tmp[2048] = { 0 }; std::va_list ap; va_start(ap,font);
+      cimg_vsnprintf(tmp,sizeof(tmp),text,ap); va_end(ap);
+      return _draw_text(x0,y0,tmp,(tc*)0,background_color,opacity,font);
+    }
+
+    //! Draw a text.
+    /**
+       \param x0 X-coordinate of the text in the image instance.
+       \param y0 Y-coordinate of the text in the image instance.
+       \param foreground_color Array of spectrum() values of type \c T, defining the foreground color (0 means 'transparent').
+       \param background_color Array of spectrum() values of type \c T, defining the background color (0 means 'transparent').
+       \param font_size Size of the font (exact match for 13,24,32,57).
+       \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_height=13, ...) {
+      if (!font_height) return *this;
+      char tmp[2048] = { 0 }; std::va_list ap; va_start(ap,font_height); cimg_vsnprintf(tmp,sizeof(tmp),text,ap); va_end(ap);
+      static CImgList<floatT> font;
+      const unsigned int
+        ref_height = font_height<=13?13:font_height<=28?24:font_height<=32?32:57,
+        padding_x = font_height<=18?1:font_height<=32?2:3;
+      if (!font || font[0]._height!=font_height) {
+        font = CImgList<floatT>::font(ref_height,true);
+        font[0].assign(1,font_height);
+        if (ref_height==font_height) cimglist_for(font,l) font[l].resize(font[l]._width + padding_x,-100,-100,-100,0);
+      }
+      if (is_empty()) {
+        if (font[0]._spectrum!=1) cimglist_for_in(font,0,255,l) font[l].channel(0);
+      } else if (font[0]._spectrum<_spectrum) cimglist_for_in(font,0,255,l) font[l].resize(-100,-100,1,_spectrum);
+      if (ref_height!=font_height) for (const char *ptrs = tmp; *ptrs; ++ptrs) {
+          const unsigned int __c = (unsigned int)(unsigned char)*ptrs, _c = (__c=='\t')?' ':__c;
+          if (_c<font._width) {
+            CImg<floatT> &c = font[_c];
+            if (c._height!=font_height) {
+              c.resize(cimg::max(1U,c._width*font_height/c._height),font_height,-100,-100,c._height>font_height?2:3);
+              c.resize(c._width + padding_x,-100,-100,-100,0);
+            }
+          }
+          if (_c+256U<font._width) {
+            CImg<floatT> &c = font[_c+256];
+            if (c._height!=font_height) {
+              c.resize(cimg::max(1U,c._width*font_height/c._height),font_height,-100,-100,c._height>font_height?2:3);
+              c.resize(c._width + padding_x,-100,-100,-100,0);
+            }
+          }
+        }
+      return _draw_text(x0,y0,tmp,foreground_color,background_color,opacity,font);
+    }
+
+    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_height=13, ...) {
+      if (!font_height) return *this;
+      cimg::unused(background_color);
+      char tmp[2048] = { 0 }; std::va_list ap; va_start(ap,font_height); cimg_vsnprintf(tmp,sizeof(tmp),text,ap); va_end(ap);
+      return draw_text(x0,y0,"%s",foreground_color,(const tc*)0,opacity,font_height,tmp);
+    }
+
+    template<typename tc>
+    CImg<T>& draw_text(const int x0, const int y0,
+                       const char *const text,
+                       const int, const tc *const background_color,
+                       const float opacity=1, const unsigned int font_height=13, ...) {
+      if (!font_height) return *this;
+      char tmp[2048] = { 0 }; std::va_list ap; va_start(ap,font_height); cimg_vsnprintf(tmp,sizeof(tmp),text,ap); va_end(ap);
+      return draw_text(x0,y0,"%s",(tc*)0,background_color,opacity,font_height,tmp);
+    }
+
+    // Draw a text (internal routine).
+    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_instance
+                                    "draw_text() : Empty specified font.",
+                                    cimg_instance);
+      const unsigned int text_length = std::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 (unsigned int i = 0; i<text_length; ++i) {
+          c = text[i];
+          switch (c) {
+          case '\n' : y+=font[0]._height; if (x>w) w = x; x = 0; break;
+          case '\t' : x+=4*font[' ']._width; break;
+          default : if (c<font._width) x+=font[c]._width;
+          }
+        }
+        if (x!=0 || c=='\n') {
+          if (x>w) w=x;
+          y+=font[0]._height;
+        }
+        assign(x0+w,y0+y,1,font[0]._spectrum,0);
+        if (background_color) cimg_forC(*this,c) get_shared_channel(c).fill((T)background_color[c]);
+      }
+
+      int x = x0, y = y0;
+      CImg<t> letter;
+      for (unsigned 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._width) {
+          letter = font[c];
+          const unsigned int cmin = cimg::min(_spectrum,letter._spectrum);
+          const CImg<t>& mask = (c+256)<(int)font._width?font[c+256]:font[c];
+          if (foreground_color)
+            for (unsigned int p = 0; p<letter._width*letter._height; ++p)
+              if (mask(p)) for (unsigned int c = 0; c<cmin; ++c) letter(p,0,0,c) = (t)(letter(p,0,0,c)*foreground_color[c]);
+          if (background_color)
+            for (unsigned int p = 0; p<letter._width*letter._height; ++p)
+              if (!mask(p)) for (unsigned int c = 0; c<cmin; ++c) letter(p,0,0,c) = (t)background_color[c];
+          if (!background_color && font._width>=512) draw_image(x,y,letter,mask,opacity,(T)1);
+          else draw_image(x,y,letter,opacity);
+          x+=letter._width;
+          }
+        }
+      }
+      return *this;
+    }
+
+    //! Draw a vector field in the image instance, using a colormap.
+    /**
+       \param flow Image of 2d vectors used as input data.
+       \param color Image of spectrum()-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 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 bool arrows=true, const unsigned int pattern=~0U) {
+      return draw_quiver(flow,CImg<t2>(color,_spectrum,1,1,1,true),opacity,sampling,factor,arrows,pattern);
+    }
+
+    //! Draw a vector field in the image instance, using a colormap.
+    /**
+       \param flow Image of 2d vectors used as input data.
+       \param color Image of spectrum()-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 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 bool arrows=true, const unsigned int pattern=~0U) {
+      if (is_empty()) return *this;
+      if (!flow || flow._spectrum!=2)
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_quiver() : Invalid dimensions of specified flow (%u,%u,%u,%u,%p).",
+                                    cimg_instance,
+                                    flow._width,flow._height,flow._depth,flow._spectrum,flow._data);
+      if (sampling<=0)
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_quiver() : Invalid sampling value %g "
+                                    "(should be >0)",
+                                    cimg_instance,
+                                    sampling);
+      const bool colorfield = (color._width==flow._width && color._height==flow._height && color._depth==1 && color._spectrum==_spectrum);
+      if (is_overlapped(flow)) return draw_quiver(+flow,color,opacity,sampling,factor,arrows,pattern);
+      float vmax,fact;
+      if (factor<=0) {
+        float m, M = (float)flow.get_norm(2).max_min(m);
+        vmax = (float)cimg::max(cimg::abs(m),cimg::abs(M));
+        if (!vmax) vmax = 1;
+        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 (arrows) {
+            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._data,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)._data,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._data,opacity,pattern);
+          }
+        }
+
+      return *this;
+    }
+
+    //! Draw a labeled horizontal axis on the image instance.
+    /**
+       \param xvalues Lower bound of the x-range.
+       \param y Y-coordinate of the horizontal axis in the image instance.
+       \param color Array of spectrum() 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.
+       \param allow_zero Enable/disable the drawing of label '0' if found.
+       \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, const unsigned int font_height=13,
+                       const bool allow_zero=true) {
+      if (is_empty()) return *this;
+      const int yt = (y+3+font_height)<_height?(y+3):(y-2-font_height);
+      const int siz = (int)xvalues.size()-1;
+      char txt[32] = { 0 };
+      CImg<T> label;
+      if (siz<=0) { // Degenerated case.
+        draw_line(0,y,_width-1,y,color,opacity,pattern);
+        if (!siz) {
+          cimg_snprintf(txt,sizeof(txt),"%g",(double)*xvalues);
+          label.assign().draw_text(0,0,txt,color,(tc*)0,opacity,font_height);
+          const int
+            _xt = (width() - label.width())/2,
+            xt = _xt<3?3:_xt+label.width()>=width()-2?width()-3-label.width():_xt;
+          draw_point(width()/2,y-1,color,opacity).draw_point(width()/2,y+1,color,opacity);
+          if (allow_zero || txt[0]!='0' || txt[1]!=0)
+            draw_text(xt,yt,txt,color,(tc*)0,opacity,font_height);
+        }
+      } else { // Regular case.
+        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);
+        cimg_foroff(xvalues,x) {
+          cimg_snprintf(txt,sizeof(txt),"%g",(double)xvalues(x));
+          label.assign().draw_text(0,0,txt,color,(tc*)0,opacity,font_height);
+          const int
+            xi = (int)(x*(_width-1)/siz),
+            _xt = xi - label.width()/2,
+            xt = _xt<3?3:_xt+label.width()>=width()-2?width()-3-label.width():_xt;
+          draw_point(xi,y-1,color,opacity).draw_point(xi,y+1,color,opacity);
+          if (allow_zero || txt[0]!='0' || txt[1]!=0)
+            draw_text(xt,yt,txt,color,(tc*)0,opacity,font_height);
+        }
+      }
+      return *this;
+    }
+
+    //! Draw a labeled vertical axis on the image instance.
+    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, const unsigned int font_height=13,
+                       const bool allow_zero=true) {
+      if (is_empty()) return *this;
+      int siz = (int)yvalues.size()-1;
+      char txt[32] = { 0 };
+      CImg<T> label;
+      if (siz<=0) { // Degenerated case.
+        draw_line(x,0,x,_height-1,color,opacity,pattern);
+        if (!siz) {
+          cimg_snprintf(txt,sizeof(txt),"%g",(double)*yvalues);
+          label.assign().draw_text(0,0,txt,color,(tc*)0,opacity,font_height);
+          const int
+            _yt = (height() - label.height())/2,
+            yt = _yt<0?0:_yt+label.height()>=height()?height()-1-label.height():_yt,
+            _xt = x - 2 - label.width(),
+            xt = _xt>=0?_xt:x+3;
+          draw_point(x-1,height()/2,color,opacity).draw_point(x+1,height()/2,color,opacity);
+          if (allow_zero || txt[0]!='0' || txt[1]!=0)
+            draw_text(xt,yt,txt,color,(tc*)0,opacity,font_height);
+        }
+      } else { // Regular case.
+        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);
+        cimg_foroff(yvalues,y) {
+          cimg_snprintf(txt,sizeof(txt),"%g",(double)yvalues(y));
+          label.assign().draw_text(0,0,txt,color,(tc*)0,opacity,font_height);
+          const int
+            yi = (int)(y*(_height-1)/siz),
+            _yt = yi - label.height()/2,
+            yt = _yt<0?0:_yt+label.height()>=height()?height()-1-label.height():_yt,
+            _xt = x - 2 - label.width(),
+            xt = _xt>=0?_xt:x+3;
+          draw_point(x-1,yi,color,opacity).draw_point(x+1,yi,color,opacity);
+          if (allow_zero || txt[0]!='0' || txt[1]!=0)
+            draw_text(xt,yt,txt,color,(tc*)0,opacity,font_height);
+        }
+      }
+      return *this;
+    }
+
+    //! Draw a labeled horizontal+vertical axis on the image instance.
+    template<typename tx, typename ty, typename tc>
+    CImg<T>& draw_axes(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,
+                       const unsigned int font_height=13, const bool allow_zero=true) {
+      if (is_empty()) return *this;
+      const CImg<tx> nxvalues(xvalues._data,xvalues.size(),1,1,1,true);
+      const int sizx = (int)xvalues.size()-1, wm1 = width()-1;
+      if (sizx>=0) {
+        float ox = (float)*nxvalues;
+        for (unsigned int x = sizx?1:0; 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,font_height,allow_zero); break; }
+          ox = nx;
+        }
+      }
+      const CImg<ty> nyvalues(yvalues._data,yvalues.size(),1,1,1,true);
+      const int sizy = (int)yvalues.size()-1, hm1 = height()-1;
+      if (sizy>0) {
+        float oy = (float)nyvalues[0];
+        for (unsigned int y = sizy?1:0; 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,font_height,allow_zero); break; }
+          oy = ny;
+        }
+      }
+      return *this;
+    }
+
+    //! Draw a labeled horizontal+vertical axis on the image instance.
+    template<typename tc>
+    CImg<T>& draw_axes(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,
+                       const unsigned int font_height=13) {
+      if (is_empty()) return *this;
+      const bool allow_zero = (x0*x1>0) || (y0*y1>0);
+      const float
+        dx = cimg::abs(x1-x0), dy = cimg::abs(y1-y0),
+        px = dx<=0?1:precisionx==0?(float)std::pow(10.0,(int)std::log10(dx)-2.0):precisionx,
+        py = dy<=0?1:precisiony==0?(float)std::pow(10.0,(int)std::log10(dy)-2.0):precisiony;
+      if (x0!=x1 && y0!=y1)
+        draw_axes(CImg<floatT>::sequence(subdivisionx>0?subdivisionx:1-width()/subdivisionx,x0,x1).round(px),
+                  CImg<floatT>::sequence(subdivisiony>0?subdivisiony:1-height()/subdivisiony,y0,y1).round(py),
+                  color,opacity,patternx,patterny,font_height,allow_zero);
+      else if (x0==x1 && y0!=y1)
+        draw_axis((int)x0,CImg<floatT>::sequence(subdivisiony>0?subdivisiony:1-height()/subdivisiony,y0,y1).round(py),
+                  color,opacity,patterny,font_height);
+      else if (x0!=x1 && y0==y1)
+        draw_axis(CImg<floatT>::sequence(subdivisionx>0?subdivisionx:1-width()/subdivisionx,x0,x1).round(px),(int)y0,
+                  color,opacity,patternx,font_height);
+      return *this;
+    }
+
+    //! 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()) return *this;
+      if (xvalues) cimg_foroff(xvalues,x) {
+          const int xi = (int)xvalues[x];
+          if (xi>=0 && xi<width()) 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<height()) draw_line(0,yi,_width-1,yi,color,opacity,patterny);
+        }
+      return *this;
+    }
+
+    //! 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) {
+      if (is_empty()) return *this;
+      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 a 1d graph on the image instance.
+    /**
+       \param data Image containing the graph values I = f(x).
+       \param color Array of spectrum() values of type \c T, defining the drawing color.
+       \param opacity Drawing opacity.
+
+       \param plot_type Define the type of the plot :
+                      - 0 = No plot.
+                      - 1 = Plot using segments.
+                      - 2 = Plot using cubic splines.
+                      - 3 = Plot with bars.
+       \param vertex_type Define the type of points :
+                      - 0 = No points.
+                      - 1 = Point.
+                      - 2 = Straight cross.
+                      - 3 = Diagonal cross.
+                      - 4 = Filled circle.
+                      - 5 = Outlined circle.
+                      - 6 = Square.
+                      - 7 = Diamond.
+       \param ymin Lower bound of the y-range.
+       \param ymax Upper bound of the y-range.
+       \param pattern Drawing pattern.
+       \note
+         - if \c ymin==ymax==0, the y-range is computed automatically from the input samples.
+    **/
+    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 int vertex_type=1,
+                        const double ymin=0, const double ymax=0, const unsigned int pattern=~0U) {
+      if (is_empty() || _height<=1) return *this;
+      if (!color)
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_graph() : Specified color is (null).",
+                                    cimg_instance);
+
+      // Create shaded colors for displaying bar plots.
+      CImg<tc> color1, color2;
+      if (plot_type==3) {
+        color1.assign(_spectrum); color2.assign(_spectrum);
+        cimg_forC(*this,c) { color1[c] = (tc)cimg::min((float)cimg::type<tc>::max(),color[c]*1.2f); color2[c] = (tc)(color[c]*0.4f); }
+      }
+
+      // Compute min/max and normalization factors.
+      const unsigned int
+        siz = data.size(),
+        _siz1 = siz - (plot_type!=3?1:0),
+        siz1 = _siz1?_siz1:1,
+        _width1 = _width - (plot_type!=3?1:0),
+        width1 = _width1?_width1:1;
+      double m = ymin, M = ymax;
+      if (ymin==ymax) m = (double)data.max_min(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);
+        if (siz==1) {
+          const int Y = (int)((*data-m)/ca);
+          draw_line(0,Y,width()-1,Y,color,opacity,pattern);
+        } else for (unsigned int off = 1; off<siz; ++off) {
+            const int
+              X = off*_width1/siz1,
+              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._data,siz,1,1,1,true);
+        int oY = (int)((data[0]-m)/ca);
+        cimg_forX(*this,x) {
+          const int Y = (int)((ndata._cubic_atX((float)x*siz1/width1)-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,color,opacity).
+            draw_line(oX,Y,oX,Y0,color2.data(),opacity).
+            draw_line(oX,Y0,X,Y0,Y<=Y0?color2.data():color1.data(),opacity).
+            draw_line(X,Y,X,Y0,color1.data(),opacity).
+            draw_line(oX,Y,X,Y,Y<=Y0?color1.data():color2.data(),opacity);
+          oX = X+1;
+        }
+      } break;
+      default : break; // No edges
+      }
+
+      // Draw graph points
+      const unsigned int wb2 = plot_type==3?_width1/(2*siz):0;
+      switch (vertex_type%8) {
+      case 1 : { // Point
+        cimg_foroff(data,off) {
+          const int
+            X = off*_width1/siz1 + wb2,
+            Y = (int)((data[off]-m)/ca);
+          draw_point(X,Y,color,opacity);
+        }
+      } break;
+      case 2 : { // Straight Cross
+        cimg_foroff(data,off) {
+          const int
+            X = off*_width1/siz1 + wb2,
+            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 : { // Diagonal Cross
+        cimg_foroff(data,off) {
+          const int
+            X = off*_width1/siz1 + wb2,
+            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*_width1/siz1 + wb2,
+            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*_width1/siz1 + wb2,
+            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*_width1/siz1 + wb2,
+            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*_width1/siz1 + wb2,
+            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 points
+      }
+      return *this;
+    }
+
+    //! Draw a 3d filled region starting from a point (\c x,\c y,\ z) in the image instance.
+    /**
+       \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 spectrum() 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._data + _spectrum, *ptrs = data(x,y,z) + siz; \
+  for (unsigned int i = _spectrum; res && i; --i) { ptrs-=whd; 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 = data(x,y,z); \
+  if (opacity>=1) cimg_forC(*this,c) { *ptrd = (T)*(col++); ptrd+=whd; } \
+  else cimg_forC(*this,c) { *ptrd = (T)(*(col++)*nopacity + *ptrd*copacity); ptrd+=whd; } \
+}
+
+#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.data(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_instance
+                                    "draw_fill() : Specified color is (null).",
+                                    cimg_instance);
+
+      region.assign(_width,_height,_depth,1,(t)0);
+      if (x>=0 && x<width() && y>=0 && y<height() && z>=0 && z<depth()) {
+        const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
+        const unsigned int whd = _width*_height*_depth, siz = _spectrum*whd, W1 = _width-1, H1 = _height-1, D1 = _depth-1;
+        const bool is_3d = (_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 (is_3d) do { // 3d version of the filling algorithm
+          const unsigned int *pcurr = remaining.data(0,posr0++), xc = *(pcurr++), yc = *(pcurr++), zc = *(pcurr++);
+          if (posr0>=512) { remaining.shift(0,-(int)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.data(0,posr0++), xc = *(pcurr++), yc = *(pcurr++);
+          if (posr0>=512) { remaining.shift(0,-(int)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,ptrd,t) if (*ptrd==noregion) *ptrd = (t)0;
+      }
+      return *this;
+    }
+
+    //! Draw a 3d filled region starting from a point (\c x,\c y,\ z) in the image instance.
+    /**
+       \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 spectrum() 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 2d filled region starting from a point (\c x,\c y) in the image instance.
+    /**
+       \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 spectrum() 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 plasma random texture.
+    /**
+       \param alpha Alpha-parameter.
+       \param beta Beta-parameter.
+       \param scale Scale-parameter.
+    **/
+    CImg<T>& draw_plasma(const float alpha=1, const float beta=0, const unsigned int scale=8) {
+      if (is_empty()) return *this;
+      const int w = width(), h = height();
+      const Tfloat m = (Tfloat)cimg::type<T>::min(), M = (Tfloat)cimg::type<T>::max();
+      cimg_forZC(*this,z,c) {
+        CImg<T> ref = get_shared_plane(z,c);
+        for (int d=1<<cimg::min(scale,31U); d>1; d>>=1) {
+          const int d2 = d>>1;
+          const float r = alpha*d + beta;
+          for (int y0=0; y0<h; y0+=d)
+            for (int x0=0; x0<w; x0+=d) {
+              const int x1 = (x0 + d)%w, y1 = (y0 + d)%h, xc = (x0 + d2)%w, yc = (y0 + d2)%h;
+              const Tfloat val = (Tfloat)(0.25f*(ref(x0,y0) + ref(x0,y1) + ref(x0,y1) + ref(x1,y1)) + r*cimg::crand());
+              ref(xc,yc) = (T)(val<m?m:val>M?M:val);
+            }
+          for (int y=-d2; y<h; y+=d)
+            for (int x0=0; x0<w; x0+=d) {
+              const int y0 = cimg::mod(y,h), x1 = (x0 + d)%w, y1 = (y + d)%h, xc = (x0 + d2)%w, yc = (y + d2)%h;
+              const Tfloat val = (Tfloat)(0.25f*(ref(xc,y0) + ref(x0,yc) + ref(xc,y1) + ref(x1,yc)) + r*cimg::crand());
+              ref(xc,yc) = (T)(val<m?m:val>M?M:val);
+            }
+          for (int y0=0; y0<h; y0+=d)
+            for (int x=-d2; x<w; x+=d) {
+              const int x0 = cimg::mod(x,w), x1 = (x + d)%w, y1 = (y0 + d)%h, xc = (x + d2)%w, yc = (y0 + d2)%h;
+              const Tfloat val = (Tfloat)(0.25f*(ref(xc,y0) + ref(x0,yc) + ref(xc,y1) + ref(x1,yc)) + r*cimg::crand());
+              ref(xc,yc) = (T)(val<m?m:val>M?M:val);
+            }
+          for (int y=-d2; y<h; y+=d)
+            for (int x=-d2; x<w; x+=d) {
+              const int x0 = cimg::mod(x,w), y0 = cimg::mod(y,h), x1 = (x + d)%w, y1 = (y + d)%h, xc = (x + d2)%w, yc = (y + d2)%h;
+              const Tfloat val = (Tfloat)(0.25f*(ref(xc,y0) + ref(x0,yc) + ref(xc,y1) + ref(x1,yc)) + r*cimg::crand());
+              ref(xc,yc) = (T)(val<m?m:val>M?M:val);
+            }
+        }
+      }
+      return *this;
+    }
+
+    //! 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 iteration_max=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._spectrum,1,1,color_palette._spectrum,true);
+      if (palette && palette._spectrum!=_spectrum)
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_mandelbrot() : Instance and specified color palette (%u,%u,%u,%u,%p) have incompatible dimensions.",
+                                    cimg_instance,
+                                    color_palette._width,color_palette._height,color_palette._depth,color_palette._spectrum,color_palette._data);
+
+      const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0), ln2 = (float)std::log(2.0);
+      unsigned int iteration = 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 (iteration=1; zr*zr + zi*zi<=4 && iteration<=iteration_max; ++iteration) {
+          const double temp = zr*zr - zi*zi + cr;
+          zi = 2*zr*zi + ci;
+          zr = temp;
+        }
+        if (iteration>iteration_max) {
+          if (palette) {
+            if (opacity>=1) cimg_forC(*this,c) (*this)(p,q,0,c) = (T)palette(0,c);
+            else cimg_forC(*this,c) (*this)(p,q,0,c) = (T)(palette(0,c)*nopacity + (*this)(p,q,0,c)*copacity);
+          } else {
+            if (opacity>=1) cimg_forC(*this,c) (*this)(p,q,0,c) = (T)0;
+            else cimg_forC(*this,c) (*this)(p,q,0,c) = (T)((*this)(p,q,0,c)*copacity);
+          }
+        } else if (normalized_iteration) {
+          const float
+            normz = (float)cimg::abs(zr*zr+zi*zi),
+            niteration = (float)(iteration + 1 - std::log(std::log(normz))/ln2);
+          if (palette) {
+            if (opacity>=1) cimg_forC(*this,c) (*this)(p,q,0,c) = (T)palette._linear_atX(niteration,c);
+            else cimg_forC(*this,c) (*this)(p,q,0,c) = (T)(palette._linear_atX(niteration,c)*nopacity + (*this)(p,q,0,c)*copacity);
+          } else {
+            if (opacity>=1) cimg_forC(*this,c) (*this)(p,q,0,c) = (T)niteration;
+            else cimg_forC(*this,c) (*this)(p,q,0,c) = (T)(niteration*nopacity + (*this)(p,q,0,c)*copacity);
+          }
+        } else {
+          if (palette) {
+            if (opacity>=1) cimg_forC(*this,c) (*this)(p,q,0,c) = (T)palette._atX(iteration,c);
+            else cimg_forC(*this,c) (*this)(p,q,0,c) = (T)(palette(iteration,c)*nopacity + (*this)(p,q,0,c)*copacity);
+          } else {
+            if (opacity>=1) cimg_forC(*this,c) (*this)(p,q,0,c) = (T)iteration;
+            else cimg_forC(*this,c) (*this)(p,q,0,c) = (T)(iteration*nopacity + (*this)(p,q,0,c)*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 iteration_max=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,iteration_max,normalized_iteration,julia_set,paramr,parami);
+    }
+
+    //! Draw a 1d gaussian function in the image instance.
+    /**
+       \param xc = X-coordinate of the gaussian center.
+       \param sigma = Standard variation of the gaussian distribution.
+       \param color = array of spectrum() 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_instance
+                                    "draw_gaussian() : Specified color is (null).",
+                                    cimg_instance);
+      const float sigma2 = 2*sigma*sigma, nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
+      const unsigned int whd = _width*_height*_depth;
+      const tc *col = color;
+      cimg_forX(*this,x) {
+        const float dx = (x - xc), val = (float)std::exp(-dx*dx/sigma2);
+        T *ptrd = data(x,0,0,0);
+        if (opacity>=1) cimg_forC(*this,c) { *ptrd = (T)(val*(*col++)); ptrd+=whd; }
+        else cimg_forC(*this,c) { *ptrd = (T)(nopacity*val*(*col++) + *ptrd*copacity); ptrd+=whd; }
+        col-=_spectrum;
+      }
+      return *this;
+    }
+
+    //! 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 spectrum() 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;
+      if (tensor._width!=2 || tensor._height!=2 || tensor._depth!=1 || tensor._spectrum!=1)
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_gaussian() : Specified tensor (%u,%u,%u,%u,%p) is not a 2x2 matrix.",
+                                    cimg_instance,
+                                    tensor._width,tensor._height,tensor._depth,tensor._spectrum,tensor._data);
+      if (!color)
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_gaussian() : Specified color is (null).",
+                                    cimg_instance);
+      typedef typename CImg<t>::Tfloat tfloat;
+      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 whd = _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)std::exp(a*dx*dx + b*dx*dy + c*dy*dy);
+          T *ptrd = data(x,y,0,0);
+          if (opacity>=1) cimg_forC(*this,c) { *ptrd = (T)(val*(*col++)); ptrd+=whd; }
+          else cimg_forC(*this,c) { *ptrd = (T)(nopacity*val*(*col++) + *ptrd*copacity); ptrd+=whd; }
+          col-=_spectrum;
+          ++dx;
+        }
+        ++dy;
+      }
+      return *this;
+    }
+
+    //! 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 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 spectrum() 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 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 spectrum() 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<t>::Tfloat tfloat;
+      if (tensor._width!=3 || tensor._height!=3 || tensor._depth!=1 || tensor._spectrum!=1)
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_gaussian() : Specified tensor (%u,%u,%u,%u,%p) is not a 3x3 matrix.",
+                                    cimg_instance,
+                                    tensor._width,tensor._height,tensor._depth,tensor._spectrum,tensor._data);
+
+      const CImg<tfloat> invT = tensor.get_invert(), invT2 = (invT*invT)/(-2.0);
+      const tfloat a = invT2(0,0), b = 2*invT2(1,0), c = 2*invT2(2,0), d = invT2(1,1), e = 2*invT2(2,1), f = invT2(2,2);
+      const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
+      const unsigned int whd = _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)std::exp(a*dx*dx + b*dx*dy + c*dx*dz + d*dy*dy + e*dy*dz + f*dz*dz);
+        T *ptrd = data(x,y,z,0);
+        if (opacity>=1) cimg_forC(*this,c) { *ptrd = (T)(val*(*col++)); ptrd+=whd; }
+        else cimg_forC(*this,c) { *ptrd = (T)(nopacity*val*(*col++) + *ptrd*copacity); ptrd+=whd; }
+        col-=_spectrum;
+      }
+      return *this;
+    }
+
+    //! 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 spectrum() 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 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 vertices = Image Nx3 describing 3d point coordinates
+       \param primitives = List of P primitives
+       \param colors = List of P color (or textures)
+       \param opacities = Image or list 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 (0 for parallel projection)
+       \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>& vertices, 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=-5e8,
+                           const float specular_light=0.2f, const float specular_shine=0.1f) {
+      return draw_object3d(x0,y0,z0,vertices,primitives,colors,opacities,render_type,double_sided,focale,lightx,lighty,lightz,
+                           specular_light,specular_shine,CImg<floatT>::empty());
+    }
+
+    template<typename tp, typename tf, typename tc, typename to, typename tz>
+    CImg<T>& draw_object3d(const float x0, const float y0, const float z0,
+                           const CImg<tp>& vertices, const CImgList<tf>& primitives,
+                           const CImgList<tc>& colors, const CImg<to>& opacities,
+                           const unsigned int render_type,
+                           const bool double_sided, const float focale,
+                           const float lightx, const float lighty, const float lightz,
+                           const float specular_light, const float specular_shine,
+                           CImg<tz>& zbuffer) {
+      return _draw_object3d(0,zbuffer,x0,y0,z0,vertices,primitives,colors,opacities,
+                            render_type,double_sided,focale,lightx,lighty,lightz,specular_light,specular_shine,1);
+    }
+
+#ifdef cimg_use_board
+    template<typename tp, typename tf, typename tc, typename to>
+    CImg<T>& draw_object3d(LibBoard::Board& board,
+                           const float x0, const float y0, const float z0,
+                           const CImg<tp>& vertices, const CImgList<tf>& primitives,
+                           const CImgList<tc>& colors, const CImg<to>& opacities,
+                           const unsigned int render_type=4,
+                           const bool double_sided=false, const float focale=500,
+                           const float lightx=0, const float lighty=0, const float lightz=-5e8,
+                           const float specular_light=0.2f, const float specular_shine=0.1f) {
+      return draw_object3d(board,x0,y0,z0,vertices,primitives,colors,opacities,render_type,double_sided,focale,lightx,lighty,lightz,
+                           specular_light,specular_shine,CImg<floatT>::empty());
+    }
+
+    template<typename tp, typename tf, typename tc, typename to, typename tz>
+    CImg<T>& draw_object3d(LibBoard::Board& board,
+                           const float x0, const float y0, const float z0,
+                           const CImg<tp>& vertices, const CImgList<tf>& primitives,
+                           const CImgList<tc>& colors, const CImg<to>& opacities,
+                           const unsigned int render_type,
+                           const bool double_sided, const float focale,
+                           const float lightx, const float lighty, const float lightz,
+                           const float specular_light, const float specular_shine,
+                           CImg<tz>& zbuffer) {
+      return _draw_object3d((void*)&board,zbuffer,x0,y0,z0,vertices,primitives,colors,opacities,
+                            render_type,double_sided,focale,lightx,lighty,lightz,specular_light,specular_shine,1);
+    }
+#endif
+
+    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>& vertices, 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=-5e8,
+                           const float specular_light=0.2f, const float specular_shine=0.1f) {
+      return draw_object3d(x0,y0,z0,vertices,primitives,colors,opacities,render_type,double_sided,focale,lightx,lighty,lightz,
+                           specular_light,specular_shine,CImg<floatT>::empty());
+    }
+
+    template<typename tp, typename tf, typename tc, typename to, typename tz>
+    CImg<T>& draw_object3d(const float x0, const float y0, const float z0,
+                           const CImg<tp>& vertices, const CImgList<tf>& primitives,
+                           const CImgList<tc>& colors, const CImgList<to>& opacities,
+                           const unsigned int render_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,
+                           CImg<tz>& zbuffer) {
+      return _draw_object3d(0,zbuffer,x0,y0,z0,vertices,primitives,colors,opacities,
+                            render_type,double_sided,focale,lightx,lighty,lightz,specular_light,specular_shine,1);
+    }
+
+#ifdef cimg_use_board
+    template<typename tp, typename tf, typename tc, typename to>
+    CImg<T>& draw_object3d(LibBoard::Board& board,
+                           const float x0, const float y0, const float z0,
+                           const CImg<tp>& vertices, const CImgList<tf>& primitives,
+                           const CImgList<tc>& colors, const 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=-5e8,
+                           const float specular_light=0.2f, const float specular_shine=0.1f) {
+      return draw_object3d(board,x0,y0,z0,vertices,primitives,colors,opacities,render_type,double_sided,focale,lightx,lighty,lightz,
+                           specular_light,specular_shine,CImg<floatT>::empty());
+    }
+
+    template<typename tp, typename tf, typename tc, typename to, typename tz>
+    CImg<T>& draw_object3d(LibBoard::Board& board,
+                           const float x0, const float y0, const float z0,
+                           const CImg<tp>& vertices, const CImgList<tf>& primitives,
+                           const CImgList<tc>& colors, const CImgList<to>& 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,
+                           CImg<tz>& zbuffer) {
+      return _draw_object3d((void*)&board,zbuffer,x0,y0,z0,vertices,primitives,colors,opacities,
+                            render_type,double_sided,focale,lightx,lighty,lightz,specular_light,specular_shine,1);
+    }
+#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 CImg<tp>& vertices, 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=-5e8,
+                           const float specular_light=0.2f, const float specular_shine=0.1f) {
+      return draw_object3d(x0,y0,z0,vertices,primitives,colors,CImg<floatT>::empty(),
+                           render_type,double_sided,focale,lightx,lighty,lightz,specular_light,specular_shine,CImg<floatT>::empty());
+    }
+
+    template<typename tp, typename tf, typename tc, typename tz>
+    CImg<T>& draw_object3d(const float x0, const float y0, const float z0,
+                           const CImg<tp>& vertices, const CImgList<tf>& primitives,
+                           const CImgList<tc>& colors,
+                           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,
+                           CImg<tz>& zbuffer) {
+      return draw_object3d(x0,y0,z0,vertices,primitives,colors,CImg<floatT>::empty(),
+                           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(LibBoard::Board& board,
+                           const float x0, const float y0, const float z0,
+                           const CImg<tp>& vertices, const CImgList<tf>& primitives,
+                           const CImgList<tc>& colors,
+                           const 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=-5e8,
+                           const float specular_light=0.2f, const float specular_shine=0.1f) {
+      return draw_object3d(x0,y0,z0,vertices,primitives,colors,CImg<floatT>::empty(),
+                           render_type,double_sided,focale,lightx,lighty,lightz,specular_light,specular_shine,CImg<floatT>::empty());
+    }
+
+    template<typename tp, typename tf, typename tc, typename to, typename tz>
+    CImg<T>& draw_object3d(LibBoard::Board& board,
+                           const float x0, const float y0, const float z0,
+                           const CImg<tp>& vertices, const CImgList<tf>& primitives,
+                           const CImgList<tc>& colors,
+                           const 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,
+                           CImg<tz>& zbuffer) {
+      return draw_object3d(x0,y0,z0,vertices,primitives,colors,CImg<floatT>::empty(),
+                           render_type,double_sided,focale,lightx,lighty,lightz,specular_light,specular_shine,zbuffer);
+    }
+#endif
+
+    template<typename tc, typename to>
+    void __draw_object3d(const unsigned int n_primitive, const CImgList<to>& opacities, const CImg<tc>& color,
+                         const int nx0, const int ny0, const CImg<T>& sprite, const float opac) {
+      if (n_primitive<opacities._width && opacities[n_primitive].is_sameXY(color))
+        draw_image(nx0,ny0,sprite,opacities[n_primitive].get_resize(sprite._width,sprite._height,1,sprite._spectrum,1));
+      else draw_image(nx0,ny0,sprite,opac);
+    }
+
+    template<typename tc, typename to>
+    void __draw_object3d(const unsigned int, const CImg<to>&, const CImg<tc>&,
+                         const int nx0, const int ny0, const CImg<T>& sprite, const float opac) {
+      draw_image(nx0,ny0,sprite,opac);
+    }
+
+    template<typename tz, typename tp, typename tf, typename tc, typename to>
+    CImg<T>& _draw_object3d(void *const pboard, CImg<tz>& zbuffer,
+                            const float X, const float Y, const float Z,
+                            const CImg<tp>& vertices,
+                            const CImgList<tf>& primitives,
+                            const CImgList<tc>& colors,
+                            const to& 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,
+                            const float sprite_scale) {
+      typedef typename cimg::superset2<tp,tz,float>::type tpfloat;
+      if (is_empty() || !vertices || !primitives) return *this;
+      char error_message[1024] = { 0 };
+      if (!vertices.is_object3d(primitives,colors,opacities,false,error_message))
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_object3d() : Invalid specified 3d object (%u,%u) (%s).",
+                                    cimg_instance,vertices._width,primitives._width,error_message);
+#ifndef cimg_use_board
+      if (pboard) return *this;
+#endif
+      const float
+        nspec = 1 - (specular_light<0.0f?0.0f:(specular_light>1.0f?1.0f:specular_light)),
+        nspec2 = 1 + (specular_shine<0.0f?0.0f: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.
+      CImg<floatT> light_texture;
+      if (render_type==5) {
+        if (colors._width>primitives._width) {
+          static CImg<floatT> default_light_texture;
+          static const tc *lptr = 0;
+          static tc ref_values[64] = { 0 };
+          const CImg<tc>& img = colors.back();
+          bool is_same_texture = (lptr==img._data);
+          if (is_same_texture)
+            for (unsigned int r = 0, j = 0; j<8; ++j)
+              for (unsigned int i = 0; i<8; ++i)
+                if (ref_values[r++]!=img(i*img._width/9,j*img._height/9,0,(i+j)%img._spectrum)) { is_same_texture = false; break; }
+          if (!is_same_texture || default_light_texture._spectrum<_spectrum) {
+            (default_light_texture.assign(img,false)/=255).resize(-100,-100,1,_spectrum);
+            lptr = colors.back().data();
+            for (unsigned int r = 0, j = 0; j<8; ++j)
+              for (unsigned int i = 0; i<8; ++i)
+                ref_values[r++] = img(i*img._width/9,j*img._height/9,0,(i+j)%img._spectrum);
+          }
+          light_texture.assign(default_light_texture,true);
+        } else {
+          static CImg<floatT> default_light_texture;
+          static float olightx = 0, olighty = 0, olightz = 0, ospecular_shine = 0;
+          if (!default_light_texture ||
+              lightx!=olightx || lighty!=olighty || lightz!=olightz ||
+              specular_shine!=ospecular_shine || default_light_texture._spectrum<_spectrum) {
+            default_light_texture.assign(512,512);
+            const float
+              dlx = lightx - X,
+              dly = lighty - Y,
+              dlz = lightz - Z,
+              nl = (float)std::sqrt(dlx*dlx + dly*dly + dlz*dlz),
+              nlx = default_light_texture._width/2*(1 + dlx/nl),
+              nly = default_light_texture._height/2*(1 + dly/nl),
+              white[] = { 1 };
+            default_light_texture.draw_gaussian(nlx,nly,default_light_texture._width/3.0f,white);
+            cimg_forXY(default_light_texture,x,y) {
+              const float factor = default_light_texture(x,y);
+              if (factor>nspec) default_light_texture(x,y) = cimg::min(2,nsl1*factor*factor + nsl2*factor + nsl3);
+            }
+            default_light_texture.resize(-100,-100,1,_spectrum);
+            olightx = lightx; olighty = lighty; olightz = lightz; ospecular_shine = specular_shine;
+          }
+          light_texture.assign(default_light_texture,true);
+        }
+      }
+
+      // Compute 3d to 2d projection.
+      CImg<tpfloat> projections(vertices._width,2);
+      tpfloat parallzmin = cimg::type<tpfloat>::max();
+      const float absfocale = focale?cimg::abs(focale):0;
+      if (absfocale) cimg_forX(projections,l) { // Perspective projection
+          const tpfloat
+            x = (tpfloat)vertices(l,0),
+            y = (tpfloat)vertices(l,1),
+            z = (tpfloat)vertices(l,2);
+          const tpfloat projectedz = z + Z + absfocale;
+          projections(l,1) = Y + absfocale*y/projectedz;
+          projections(l,0) = X + absfocale*x/projectedz;
+        } else cimg_forX(projections,l) { // Parallel projection
+          const tpfloat
+            x = (tpfloat)vertices(l,0),
+            y = (tpfloat)vertices(l,1),
+            z = (tpfloat)vertices(l,2);
+          if (z<parallzmin) parallzmin = z;
+          projections(l,1) = Y + y;
+          projections(l,0) = X + x;
+        }
+
+      // Compute and sort visible primitives.
+      CImg<uintT> visibles(primitives._width);
+      CImg<tpfloat> zrange(primitives._width);
+      unsigned int nb_visibles = 0;
+      const tpfloat zmin = absfocale?(tpfloat)(1.5f - absfocale):cimg::type<tpfloat>::min();
+      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 tpfloat z0 = Z + vertices(i0,2);
+          if (z0>zmin) {
+            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);
+          const tpfloat
+            x0 = projections(i0,0), y0 = projections(i0,1), z0 = Z + vertices(i0,2),
+            x1 = projections(i1,0), y1 = projections(i1,1), z1 = Z + vertices(i1,2);
+          tpfloat 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 (xM>=0 && xm<_width && yM>=0 && ym<_height && z0>zmin && z1>zmin) {
+            visibles(nb_visibles) = (unsigned int)l;
+            zrange(nb_visibles++) = (z0 + z1)/2;
+          }
+        } break;
+        case 2 : // Segment
+        case 6 : {
+          const unsigned int
+            i0 = (unsigned int)primitive(0),
+            i1 = (unsigned int)primitive(1);
+          const tpfloat
+            x0 = projections(i0,0), y0 = projections(i0,1), z0 = Z + vertices(i0,2),
+            x1 = projections(i1,0), y1 = projections(i1,1), z1 = Z + vertices(i1,2);
+          tpfloat 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 (xM>=0 && xm<_width && yM>=0 && ym<_height && z0>zmin && z1>zmin) {
+            visibles(nb_visibles) = (unsigned int)l;
+            zrange(nb_visibles++) = (z0 + z1)/2;
+          }
+        } 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 tpfloat
+            x0 = projections(i0,0), y0 = projections(i0,1), z0 = Z + vertices(i0,2),
+            x1 = projections(i1,0), y1 = projections(i1,1), z1 = Z + vertices(i1,2),
+            x2 = projections(i2,0), y2 = projections(i2,1), z2 = Z + vertices(i2,2);
+          tpfloat 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 (xM>=0 && xm<_width && yM>=0 && ym<_height && z0>zmin && z1>zmin && z2>zmin) {
+            const tpfloat 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 tpfloat
+            x0 = projections(i0,0), y0 = projections(i0,1), z0 = Z + vertices(i0,2),
+            x1 = projections(i1,0), y1 = projections(i1,1), z1 = Z + vertices(i1,2),
+            x2 = projections(i2,0), y2 = projections(i2,1), z2 = Z + vertices(i2,2),
+            x3 = projections(i3,0), y3 = projections(i3,1), z3 = Z + vertices(i3,2);
+          tpfloat 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 (xM>=0 && xm<_width && yM>=0 && ym<_height && z0>zmin && z1>zmin && z2>zmin) {
+            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_instance
+                                      "draw_object3d() : Invalid primitive[%u] with size %u "
+                                      "(should have size 1,2,3,4,5,6,9 or 12).",
+                                      cimg_instance,
+                                      l,primitive.size());
+        }
+      }
+      if (nb_visibles<=0) return *this;
+      CImg<uintT> permutations;
+      CImg<tpfloat>(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 tpfloat
+              x0 = (tpfloat)vertices(i0,0), y0 = (tpfloat)vertices(i0,1), z0 = (tpfloat)vertices(i0,2),
+              x1 = (tpfloat)vertices(i1,0), y1 = (tpfloat)vertices(i1,1), z1 = (tpfloat)vertices(i1,2),
+              x2 = (tpfloat)vertices(i2,0), y2 = (tpfloat)vertices(i2,1), z2 = (tpfloat)vertices(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 = (tpfloat)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 = (tpfloat)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<tpfloat> vertices_normals(vertices._width,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 tpfloat
+              x0 = (tpfloat)vertices(i0,0), y0 = (tpfloat)vertices(i0,1), z0 = (tpfloat)vertices(i0,2),
+              x1 = (tpfloat)vertices(i1,0), y1 = (tpfloat)vertices(i1,1), z1 = (tpfloat)vertices(i1,2),
+              x2 = (tpfloat)vertices(i2,0), y2 = (tpfloat)vertices(i2,1), z2 = (tpfloat)vertices(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 = (tpfloat)(1e-5f + std::sqrt(nnx*nnx + nny*nny + nnz*nnz)),
+              nx = nnx/norm,
+              ny = nny/norm,
+              nz = nnz/norm;
+            vertices_normals(i0,0)+=nx; vertices_normals(i0,1)+=ny; vertices_normals(i0,2)+=nz;
+            vertices_normals(i1,0)+=nx; vertices_normals(i1,1)+=ny; vertices_normals(i1,2)+=nz;
+            vertices_normals(i2,0)+=nx; vertices_normals(i2,1)+=ny; vertices_normals(i2,2)+=nz;
+            if (rectangle_flag) { vertices_normals(i3,0)+=nx; vertices_normals(i3,1)+=ny; vertices_normals(i3,2)+=nz; }
+          }
+        }
+
+        if (double_sided) cimg_forX(vertices_normals,p) if (vertices_normals(p,2)>0) {
+          vertices_normals(p,0) = -vertices_normals(p,0);
+          vertices_normals(p,1) = -vertices_normals(p,1);
+          vertices_normals(p,2) = -vertices_normals(p,2);
+        }
+
+        if (render_type==4) {
+          lightprops.assign(vertices._width);
+          cimg_forX(lightprops,l) {
+            const tpfloat
+              nx = vertices_normals(l,0),
+              ny = vertices_normals(l,1),
+              nz = vertices_normals(l,2),
+              norm = (tpfloat)std::sqrt(1e-5f + nx*nx + ny*ny + nz*nz),
+              lx = X + vertices(l,0) - lightx,
+              ly = Y + vertices(l,1) - lighty,
+              lz = Z + vertices(l,2) - lightz,
+              nl = (tpfloat)std::sqrt(1e-5f + lx*lx + ly*ly + lz*lz),
+              factor = cimg::max((-lx*nx-ly*ny-lz*nz)/(norm*nl),0);
+            lightprops[l] = 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(vertices._width,2);
+          cimg_forX(lightprops,l) {
+            const tpfloat
+              nx = vertices_normals(l,0),
+              ny = vertices_normals(l,1),
+              nz = vertices_normals(l,2),
+              norm = (tpfloat)std::sqrt(1e-5f + nx*nx + ny*ny + nz*nz),
+              nnx = nx/norm,
+              nny = ny/norm;
+            lightprops(l,0) = lw2*(1 + nnx);
+            lightprops(l,1) = lh2*(1 + nny);
+          }
+        }
+      } break;
+      }
+
+      // Draw visible primitives
+      const float _focale = absfocale?absfocale:(1-parallzmin);
+      const CImg<tc> default_color(1,_spectrum,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._width?colors[n_primitive]:CImg<tc>(),
+          _color = (__color && __color.size()!=_spectrum && __color._spectrum<_spectrum)?colors[n_primitive].get_resize(-100,-100,-100,_spectrum,0):CImg<tc>(),
+          &color = _color?_color:(__color?__color:default_color);
+        const tc *const pcolor = color._data;
+        const float opac = n_primitive<opacities.size()?opacities(n_primitive,0):1.0f;
+#ifdef cimg_use_board
+        LibBoard::Board &board = *(LibBoard::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()==_spectrum) { // Colored point.
+            draw_point(x0,y0,pcolor,opac);
+#ifdef cimg_use_board
+            if (pboard) {
+              board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opac*255));
+              board.fillCircle((float)x0,height()-(float)y0,0);
+            }
+#endif
+          } else { // Colored sprite.
+            if (!__color)
+              throw CImgArgumentException(_cimg_instance
+                                          "draw_object3d() : Undefined texture for sprite primitive [%u].",
+                                          cimg_instance,n_primitive);
+            const tpfloat z = Z + vertices(n0,2);
+            const float factor = focale<0?1:sprite_scale*(absfocale?absfocale/(z + absfocale):1);
+            const unsigned int
+              _sw = (unsigned int)(color._width*factor),
+              _sh = (unsigned int)(color._height*factor),
+              sw = _sw?_sw:1,
+              sh = _sh?_sh:1;
+            const int nx0 = x0 - (int)sw/2, ny0 = y0 - (int)sh/2;
+            if (sw<_width && sh<_height && (nx0+(int)sw/2>=0 || nx0-(int)sw/2<width() || ny0+(int)sh/2>=0 || ny0-(int)sh/2<height())) {
+              const CImg<tc>
+                _sprite = (sw!=color._width || sh!=color._height)?color.get_resize(sw,sh,1,-100,render_type<=3?1:3):CImg<tc>(),
+                &sprite = _sprite?_sprite:color;
+              __draw_object3d(n_primitive,opacities,color,nx0,ny0,sprite,opac);
+#ifdef cimg_use_board
+              if (pboard) {
+                board.setPenColorRGBi(128,128,128);
+                board.setFillColor(LibBoard::Color::None);
+                board.drawRectangle((float)nx0,height()-(float)ny0,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 = vertices(n0,2) + Z + _focale,
+            z1 = vertices(n1,2) + Z + _focale;
+          if (render_type) {
+            if (zbuffer) draw_line(zbuffer,x0,y0,z0,x1,y1,z1,pcolor,opac);
+            else draw_line(x0,y0,x1,y1,pcolor,opac);
+#ifdef cimg_use_board
+            if (pboard) {
+              board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opac*255));
+              board.drawLine((float)x0,height()-(float)y0,x1,height()-(float)y1);
+            }
+#endif
+          } else {
+            draw_point(x0,y0,pcolor,opac).draw_point(x1,y1,pcolor,opac);
+#ifdef cimg_use_board
+            if (pboard) {
+              board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opac*255));
+              board.drawCircle((float)x0,height()-(float)y0,0);
+              board.drawCircle((float)x1,height()-(float)y1,0);
+            }
+#endif
+          }
+        } break;
+        case 5 : { // Colored sphere
+          const unsigned int
+            n0 = (unsigned int)primitive[0],
+            n1 = (unsigned int)primitive[1];
+          const float
+            Xc = 0.5f*((float)vertices(n0,0) + (float)vertices(n1,0)),
+            Yc = 0.5f*((float)vertices(n0,1) + (float)vertices(n1,1)),
+            Zc = 0.5f*((float)vertices(n0,2) + (float)vertices(n1,2)),
+            zc = Z + Zc + _focale,
+            xc = X + Xc*(absfocale?absfocale/zc:1),
+            yc = Y + Yc*(absfocale?absfocale/zc:1),
+            radius = std::sqrt(cimg::sqr(Xc-vertices(n0,0)) + cimg::sqr(Yc-vertices(n0,1)) + cimg::sqr(Zc-vertices(n0,2)))*(absfocale?absfocale/zc:1);
+          switch (render_type) {
+          case 0 :
+            draw_point((int)xc,(int)yc,pcolor,opac);
+#ifdef cimg_use_board
+            if (pboard) {
+              board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opac*255));
+              board.fillCircle(xc,height()-yc,0);
+            }
+#endif
+            break;
+          case 1 :
+            draw_circle((int)xc,(int)yc,(int)radius,pcolor,opac,~0U);
+#ifdef cimg_use_board
+            if (pboard) {
+              board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opac*255));
+              board.setFillColor(LibBoard::Color::None);
+              board.drawCircle(xc,height()-yc,radius);
+            }
+#endif
+            break;
+          default :
+            draw_circle((int)xc,(int)yc,(int)radius,pcolor,opac);
+#ifdef cimg_use_board
+            if (pboard) {
+              board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opac*255));
+              board.fillCircle(xc,height()-yc,radius);
+            }
+#endif
+            break;
+          }
+        } break;
+        case 6 : { // Textured line
+          if (!__color)
+            throw CImgArgumentException(_cimg_instance
+                                        "draw_object3d() : Undefined texture for line primitive [%u].",
+                                        cimg_instance,n_primitive);
+          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 = vertices(n0,2) + Z + _focale,
+            z1 = vertices(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,height()-(float)y0,(float)x1,height()-(float)y1);
+            }
+#endif
+          } else {
+            draw_point(x0,y0,color.get_vector_at(tx0,ty0)._data,opac).
+              draw_point(x1,y1,color.get_vector_at(tx1,ty1)._data,opac);
+#ifdef cimg_use_board
+            if (pboard) {
+              board.setPenColorRGBi(128,128,128,(unsigned char)(opac*255));
+              board.drawCircle((float)x0,height()-(float)y0,0);
+              board.drawCircle((float)x1,height()-(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 = vertices(n0,2) + Z + _focale,
+            z1 = vertices(n1,2) + Z + _focale,
+            z2 = vertices(n2,2) + Z + _focale;
+          switch (render_type) {
+          case 0 :
+            draw_point(x0,y0,pcolor,opac).draw_point(x1,y1,pcolor,opac).draw_point(x2,y2,pcolor,opac);
+#ifdef cimg_use_board
+            if (pboard) {
+              board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opac*255));
+              board.drawCircle((float)x0,height()-(float)y0,0);
+              board.drawCircle((float)x1,height()-(float)y1,0);
+              board.drawCircle((float)x2,height()-(float)y2,0);
+            }
+#endif
+            break;
+          case 1 :
+            if (zbuffer)
+              draw_line(zbuffer,x0,y0,z0,x1,y1,z1,pcolor,opac).draw_line(zbuffer,x0,y0,z0,x2,y2,z2,pcolor,opac).
+                draw_line(zbuffer,x1,y1,z1,x2,y2,z2,pcolor,opac);
+            else
+              draw_line(x0,y0,x1,y1,pcolor,opac).draw_line(x0,y0,x2,y2,pcolor,opac).
+                draw_line(x1,y1,x2,y2,pcolor,opac);
+#ifdef cimg_use_board
+            if (pboard) {
+              board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opac*255));
+              board.drawLine((float)x0,height()-(float)y0,(float)x1,height()-(float)y1);
+              board.drawLine((float)x0,height()-(float)y0,(float)x2,height()-(float)y2);
+              board.drawLine((float)x1,height()-(float)y1,(float)x2,height()-(float)y2);
+            }
+#endif
+            break;
+          case 2 :
+            if (zbuffer) draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,pcolor,opac);
+            else draw_triangle(x0,y0,x1,y1,x2,y2,pcolor,opac);
+#ifdef cimg_use_board
+            if (pboard) {
+              board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opac*255));
+              board.fillTriangle((float)x0,height()-(float)y0,(float)x1,height()-(float)y1,(float)x2,height()-(float)y2);
+            }
+#endif
+            break;
+          case 3 :
+            if (zbuffer) draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,pcolor,opac,lightprops(l));
+            else _draw_triangle(x0,y0,x1,y1,x2,y2,pcolor,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,height()-(float)y0,(float)x1,height()-(float)y1,(float)x2,height()-(float)y2);
+            }
+#endif
+            break;
+          case 4 :
+            if (zbuffer) draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,pcolor,lightprops(n0),lightprops(n1),lightprops(n2),opac);
+            else draw_triangle(x0,y0,x1,y1,x2,y2,pcolor,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,height()-(float)y0,lightprops(n0),
+                                         (float)x1,height()-(float)y1,lightprops(n1),
+                                         (float)x2,height()-(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,pcolor,light_texture,lx0,ly0,lx1,ly1,lx2,ly2,opac);
+            else draw_triangle(x0,y0,x1,y1,x2,y2,pcolor,light_texture,lx0,ly0,lx1,ly1,lx2,ly2,opac);
+#ifdef cimg_use_board
+            if (pboard) {
+              const float
+                l0 = light_texture((int)(light_texture.width()/2*(1+lightprops(n0,0))), (int)(light_texture.height()/2*(1+lightprops(n0,1)))),
+                l1 = light_texture((int)(light_texture.width()/2*(1+lightprops(n1,0))), (int)(light_texture.height()/2*(1+lightprops(n1,1)))),
+                l2 = light_texture((int)(light_texture.width()/2*(1+lightprops(n2,0))), (int)(light_texture.height()/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,height()-(float)y0,l0,
+                                         (float)x1,height()-(float)y1,l1,
+                                         (float)x2,height()-(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 = vertices(n0,2) + Z + _focale,
+            z1 = vertices(n1,2) + Z + _focale,
+            z2 = vertices(n2,2) + Z + _focale,
+            z3 = vertices(n3,2) + Z + _focale;
+          switch (render_type) {
+          case 0 :
+            draw_point(x0,y0,pcolor,opac).draw_point(x1,y1,pcolor,opac).
+              draw_point(x2,y2,pcolor,opac).draw_point(x3,y3,pcolor,opac);
+#ifdef cimg_use_board
+            if (pboard) {
+              board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opac*255));
+              board.drawCircle((float)x0,height()-(float)y0,0);
+              board.drawCircle((float)x1,height()-(float)y1,0);
+              board.drawCircle((float)x2,height()-(float)y2,0);
+              board.drawCircle((float)x3,height()-(float)y3,0);
+            }
+#endif
+            break;
+          case 1 :
+            if (zbuffer)
+              draw_line(zbuffer,x0,y0,z0,x1,y1,z1,pcolor,opac).draw_line(zbuffer,x1,y1,z1,x2,y2,z2,pcolor,opac).
+                draw_line(zbuffer,x2,y2,z2,x3,y3,z3,pcolor,opac).draw_line(zbuffer,x3,y3,z3,x0,y0,z0,pcolor,opac);
+            else
+              draw_line(x0,y0,x1,y1,pcolor,opac).draw_line(x1,y1,x2,y2,pcolor,opac).
+                draw_line(x2,y2,x3,y3,pcolor,opac).draw_line(x3,y3,x0,y0,pcolor,opac);
+#ifdef cimg_use_board
+            if (pboard) {
+              board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opac*255));
+              board.drawLine((float)x0,height()-(float)y0,(float)x1,height()-(float)y1);
+              board.drawLine((float)x1,height()-(float)y1,(float)x2,height()-(float)y2);
+              board.drawLine((float)x2,height()-(float)y2,(float)x3,height()-(float)y3);
+              board.drawLine((float)x3,height()-(float)y3,(float)x0,height()-(float)y0);
+            }
+#endif
+            break;
+          case 2 :
+            if (zbuffer)
+              draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,pcolor,opac).draw_triangle(zbuffer,x0,y0,z0,x2,y2,z2,x3,y3,z3,pcolor,opac);
+            else
+              draw_triangle(x0,y0,x1,y1,x2,y2,pcolor,opac).draw_triangle(x0,y0,x2,y2,x3,y3,pcolor,opac);
+#ifdef cimg_use_board
+            if (pboard) {
+              board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opac*255));
+              board.fillTriangle((float)x0,height()-(float)y0,(float)x1,height()-(float)y1,(float)x2,height()-(float)y2);
+              board.fillTriangle((float)x0,height()-(float)y0,(float)x2,height()-(float)y2,(float)x3,height()-(float)y3);
+            }
+#endif
+            break;
+          case 3 :
+            if (zbuffer)
+              draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,pcolor,opac,lightprops(l)).
+                draw_triangle(zbuffer,x0,y0,z0,x2,y2,z2,x3,y3,z3,pcolor,opac,lightprops(l));
+            else
+              _draw_triangle(x0,y0,x1,y1,x2,y2,pcolor,opac,lightprops(l)).
+                _draw_triangle(x0,y0,x2,y2,x3,y3,pcolor,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,height()-(float)y0,(float)x1,height()-(float)y1,(float)x2,height()-(float)y2);
+              board.fillTriangle((float)x0,height()-(float)y0,(float)x2,height()-(float)y2,(float)x3,height()-(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,pcolor,lightprop0,lightprop1,lightprop2,opac).
+                draw_triangle(zbuffer,x0,y0,z0,x2,y2,z2,x3,y3,z3,pcolor,lightprop0,lightprop2,lightprop3,opac);
+            else
+              draw_triangle(x0,y0,x1,y1,x2,y2,pcolor,lightprop0,lightprop1,lightprop2,opac).
+                draw_triangle(x0,y0,x2,y2,x3,y3,pcolor,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,height()-(float)y0,lightprop0,
+                                         (float)x1,height()-(float)y1,lightprop1,
+                                         (float)x2,height()-(float)y2,lightprop2);
+              board.fillGouraudTriangle((float)x0,height()-(float)y0,lightprop0,
+                                         (float)x2,height()-(float)y2,lightprop2,
+                                         (float)x3,height()-(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,pcolor,light_texture,lx0,ly0,lx1,ly1,lx2,ly2,opac).
+                draw_triangle(zbuffer,x0,y0,z0,x2,y2,z2,x3,y3,z3,pcolor,light_texture,lx0,ly0,lx2,ly2,lx3,ly3,opac);
+            else
+              draw_triangle(x0,y0,x1,y1,x2,y2,pcolor,light_texture,lx0,ly0,lx1,ly1,lx2,ly2,opac).
+                draw_triangle(x0,y0,x2,y2,x3,y3,pcolor,light_texture,lx0,ly0,lx2,ly2,lx3,ly3,opac);
+#ifdef cimg_use_board
+            if (pboard) {
+              const float
+                l0 = light_texture((int)(light_texture.width()/2*(1+lx0)), (int)(light_texture.height()/2*(1+ly0))),
+                l1 = light_texture((int)(light_texture.width()/2*(1+lx1)), (int)(light_texture.height()/2*(1+ly1))),
+                l2 = light_texture((int)(light_texture.width()/2*(1+lx2)), (int)(light_texture.height()/2*(1+ly2))),
+                l3 = light_texture((int)(light_texture.width()/2*(1+lx3)), (int)(light_texture.height()/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,height()-(float)y0,l0,
+                                         (float)x1,height()-(float)y1,l1,
+                                         (float)x2,height()-(float)y2,l2);
+              board.fillGouraudTriangle((float)x0,height()-(float)y0,l0,
+                                         (float)x2,height()-(float)y2,l2,
+                                         (float)x3,height()-(float)y3,l3);
+            }
+#endif
+          } break;
+          }
+        } break;
+        case 9 : { // Textured triangle
+          if (!__color)
+            throw CImgArgumentException(_cimg_instance
+                                        "draw_object3d() : Undefined texture for triangle primitive [%u].",
+                                        cimg_instance,n_primitive);
+          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 = vertices(n0,2) + Z + _focale,
+            z1 = vertices(n1,2) + Z + _focale,
+            z2 = vertices(n2,2) + Z + _focale;
+          switch (render_type) {
+          case 0 :
+            draw_point(x0,y0,color.get_vector_at(tx0,ty0)._data,opac).
+              draw_point(x1,y1,color.get_vector_at(tx1,ty1)._data,opac).
+              draw_point(x2,y2,color.get_vector_at(tx2,ty2)._data,opac);
+#ifdef cimg_use_board
+            if (pboard) {
+              board.setPenColorRGBi(128,128,128,(unsigned char)(opac*255));
+              board.drawCircle((float)x0,height()-(float)y0,0);
+              board.drawCircle((float)x1,height()-(float)y1,0);
+              board.drawCircle((float)x2,height()-(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,height()-(float)y0,(float)x1,height()-(float)y1);
+              board.drawLine((float)x0,height()-(float)y0,(float)x2,height()-(float)y2);
+              board.drawLine((float)x1,height()-(float)y1,(float)x2,height()-(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,height()-(float)y0,(float)x1,height()-(float)y1,(float)x2,height()-(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,height()-(float)y0,(float)x1,height()-(float)y1,(float)x2,height()-(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,height()-(float)y0,lightprops(n0),
+                                        (float)x1,height()-(float)y1,lightprops(n1),
+                                        (float)x2,height()-(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.width()/2*(1+lightprops(n0,0))), (int)(light_texture.height()/2*(1+lightprops(n0,1)))),
+                l1 = light_texture((int)(light_texture.width()/2*(1+lightprops(n1,0))), (int)(light_texture.height()/2*(1+lightprops(n1,1)))),
+                l2 = light_texture((int)(light_texture.width()/2*(1+lightprops(n2,0))), (int)(light_texture.height()/2*(1+lightprops(n2,1))));
+              board.setPenColorRGBi(128,128,128,(unsigned char)(opac*255));
+              board.fillGouraudTriangle((float)x0,height()-(float)y0,l0,(float)x1,height()-(float)y1,l1,(float)x2,height()-(float)y2,l2);
+            }
+#endif
+            break;
+          }
+        } break;
+        case 12 : { // Textured quadrangle
+          if (!__color)
+            throw CImgArgumentException(_cimg_instance
+                                        "draw_object3d() : Undefined texture for quadrangle primitive [%u].",
+                                        cimg_instance,n_primitive);
+          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 = vertices(n0,2) + Z + _focale,
+            z1 = vertices(n1,2) + Z + _focale,
+            z2 = vertices(n2,2) + Z + _focale,
+            z3 = vertices(n3,2) + Z + _focale;
+
+          switch (render_type) {
+          case 0 :
+            draw_point(x0,y0,color.get_vector_at(tx0,ty0)._data,opac).
+              draw_point(x1,y1,color.get_vector_at(tx1,ty1)._data,opac).
+              draw_point(x2,y2,color.get_vector_at(tx2,ty2)._data,opac).
+              draw_point(x3,y3,color.get_vector_at(tx3,ty3)._data,opac);
+#ifdef cimg_use_board
+            if (pboard) {
+              board.setPenColorRGBi(128,128,128,(unsigned char)(opac*255));
+              board.drawCircle((float)x0,height()-(float)y0,0);
+              board.drawCircle((float)x1,height()-(float)y1,0);
+              board.drawCircle((float)x2,height()-(float)y2,0);
+              board.drawCircle((float)x3,height()-(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,height()-(float)y0,(float)x1,height()-(float)y1);
+              board.drawLine((float)x1,height()-(float)y1,(float)x2,height()-(float)y2);
+              board.drawLine((float)x2,height()-(float)y2,(float)x3,height()-(float)y3);
+              board.drawLine((float)x3,height()-(float)y3,(float)x0,height()-(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,height()-(float)y0,(float)x1,height()-(float)y1,(float)x2,height()-(float)y2);
+              board.fillTriangle((float)x0,height()-(float)y0,(float)x2,height()-(float)y2,(float)x3,height()-(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,height()-(float)y0,(float)x1,height()-(float)y1,(float)x2,height()-(float)y2);
+              board.fillTriangle((float)x0,height()-(float)y0,(float)x2,height()-(float)y2,(float)x3,height()-(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,height()-(float)y0,lightprop0,
+                                         (float)x1,height()-(float)y1,lightprop1,
+                                         (float)x2,height()-(float)y2,lightprop2);
+              board.fillGouraudTriangle((float)x0,height()-(float)y0,lightprop0,
+                                         (float)x2,height()-(float)y2,lightprop2,
+                                         (float)x3,height()-(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.width()/2*(1+lx0)), (int)(light_texture.height()/2*(1+ly0))),
+                l1 = light_texture((int)(light_texture.width()/2*(1+lx1)), (int)(light_texture.height()/2*(1+ly1))),
+                l2 = light_texture((int)(light_texture.width()/2*(1+lx2)), (int)(light_texture.height()/2*(1+ly2))),
+                l3 = light_texture((int)(light_texture.width()/2*(1+lx3)), (int)(light_texture.height()/2*(1+ly3)));
+              board.setPenColorRGBi(128,128,128,(unsigned char)(opac*255));
+              board.fillGouraudTriangle((float)x0,height()-(float)y0,l0,
+                                         (float)x1,height()-(float)y1,l1,
+                                         (float)x2,height()-(float)y2,l2);
+              board.fillGouraudTriangle((float)x0,height()-(float)y0,l0,
+                                         (float)x2,height()-(float)y2,l2,
+                                         (float)x3,height()-(float)y3,l3);
+            }
+#endif
+          } break;
+          }
+        } break;
+        }
+      }
+      return *this;
+    }
+
+    //@}
+    //---------------------------
+    //
+    //! \name Data Input
+    //@{
+    //---------------------------
+
+    //! Simple interface to select a shape from an image.
+    CImg<T>& select(CImgDisplay &disp,
+                    const unsigned int feature_type=2, unsigned int *const XYZ=0) {
+      return get_select(disp,feature_type,XYZ).move_to(*this);
+    }
+
+    //! Simple interface to select a shape from an image.
+    CImg<T>& select(const char *const title,
+                    const unsigned int feature_type=2, unsigned int *const XYZ=0) {
+      return get_select(title,feature_type,XYZ).move_to(*this);
+    }
+
+    //! Simple interface to select a shape from an image.
+    CImg<intT> get_select(CImgDisplay &disp,
+                          const unsigned int feature_type=2, unsigned int *const XYZ=0) const {
+      return _get_select(disp,0,feature_type,XYZ,0,0,0,true);
+    }
+
+    //! Simple interface to select a shape from an image.
+    CImg<intT> get_select(const char *const title,
+                          const unsigned int feature_type=2, unsigned int *const XYZ=0) const {
+      CImgDisplay disp;
+      return _get_select(disp,title,feature_type,XYZ,0,0,0,true);
+    }
+
+    CImg<intT> _get_select(CImgDisplay &disp, const char *const title,
+                           const unsigned int feature_type, unsigned int *const XYZ,
+                           const int origX, const int origY, const int origZ,
+                           const bool reset_view3d=true) const {
+      if (is_empty()) return CImg<intT>(1,feature_type==0?3:6,1,1,-1);
+      if (!disp) {
+        disp.assign(cimg_fitscreen(_width,_height,_depth),title?title:0,1);
+        if (!title) disp.set_title("CImg<%s> (%ux%ux%ux%u)",pixel_type(),_width,_height,_depth,_spectrum);
+      } else if (title) disp.set_title("%s",title);
+
+      const unsigned int old_normalization = disp.normalization();
+      bool old_is_resized = disp.is_resized();
+      disp._normalization = 0;
+      disp.show().set_key(0).set_wheel();
+
+      unsigned char foreground_color[] = { 255,255,255 }, background_color[] = { 0,0,0 };
+
+      int area = 0, starting_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, X3d = -1, Y3d = -1,
+        oX = X, oY = Y, oZ = Z, oX3d = X3d, oY3d = -1;
+      unsigned int old_button = 0, key = 0;
+
+      bool shape_selected = false, text_down = false;
+      static CImg<floatT> pose3d;
+      static bool is_view3d = false;
+      if (reset_view3d) { pose3d.assign(); is_view3d = false; }
+      CImg<floatT> points3d, opacities3d, sel_opacities3d;
+      CImgList<uintT> primitives3d, sel_primitives3d;
+      CImgList<ucharT> colors3d, sel_colors3d;
+      CImg<ucharT> visu, visu0, view3d;
+      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<0?-1:mx*(width()+(depth()>1?depth():0))/disp.width(),
+          mY = my<0?-1:my*(height()+(depth()>1?depth():0))/disp.height();
+        area = 0;
+        if (mX>=0 && mY>=0 && mX<width() && mY<height())  { area = 1; X = mX; Y = mY; Z = phase?Z1:Z0; }
+        if (mX>=0 && mX<width() && mY>=height()) { area = 2; X = mX; Z = mY - _height; Y = phase?Y1:Y0; }
+        if (mY>=0 && mX>=width() && mY<height()) { area = 3; Y = mY; Z = mX - _width; X = phase?X1:X0; }
+        if (mX>=width() && mY>=height()) area = 4;
+        if (disp.button()) { if (!clicked_area) clicked_area = area; } else clicked_area = 0;
+
+        switch (key = disp.key()) {
+#if cimg_OS!=2
+        case cimg::keyCTRLRIGHT :
+#endif
+        case 0 : case cimg::keyCTRLLEFT : key = 0; break;
+        case cimg::keyPAGEUP : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { disp.set_wheel(1); key = 0; } break;
+        case cimg::keyPAGEDOWN : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { disp.set_wheel(-1); key = 0; } break;
+        case cimg::keyD : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) {
+          disp.set_fullscreen(false).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.set_key(key,false); key = 0; visu0.assign();
+        } break;
+        case cimg::keyC : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) {
+          disp.set_fullscreen(false).resize(cimg_fitscreen(2*disp.width()/3,2*disp.height()/3,1),false)._is_resized = true;
+          disp.set_key(key,false); key = 0; visu0.assign();
+        } break;
+        case cimg::keyR : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) {
+          disp.set_fullscreen(false).resize(cimg_fitscreen(_width,_height,_depth),false)._is_resized = true;
+          disp.set_key(key,false); key = 0; visu0.assign();
+        } break;
+        case cimg::keyF : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) {
+          disp.resize(disp.screen_width(),disp.screen_height(),false).toggle_fullscreen()._is_resized = true;
+          disp.set_key(key,false); key = 0; visu0.assign();
+        } break;
+        case cimg::keyV : is_view3d = !is_view3d; disp.set_key(key,false); key = 0; visu0.assign(); break;
+        case cimg::keyS : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) {
+          static unsigned int snap_number = 0;
+          char filename[32] = { 0 };
+          std::FILE *file;
+          do {
+            cimg_snprintf(filename,sizeof(filename),cimg_appname "_%.4u.bmp",snap_number++);
+            if ((file=std::fopen(filename,"r"))!=0) cimg::fclose(file);
+          } while (file);
+          if (visu0) {
+            visu.draw_text(0,0," Saving snapshot... ",foreground_color,background_color,1,13).display(disp);
+            visu0.save(filename);
+            visu.draw_text(0,0," Snapshot '%s' saved. ",foreground_color,background_color,1,13,filename).display(disp);
+          }
+          disp.set_key(key,false); key = 0;
+        } break;
+        case cimg::keyO :
+          if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) {
+            static unsigned int snap_number = 0;
+            char filename[32] = { 0 };
+            std::FILE *file;
+            do {
+#ifdef cimg_use_zlib
+              cimg_snprintf(filename,sizeof(filename),cimg_appname "_%.4u.cimgz",snap_number++);
+#else
+              cimg_snprintf(filename,sizeof(filename),cimg_appname "_%.4u.cimg",snap_number++);
+#endif
+              if ((file=std::fopen(filename,"r"))!=0) cimg::fclose(file);
+            } while (file);
+            visu.draw_text(0,0," Saving instance... ",foreground_color,background_color,1,13).display(disp);
+            save(filename);
+            visu.draw_text(0,0," Instance '%s' saved. ",foreground_color,background_color,1,13,filename).display(disp);
+            disp.set_key(key,false); key = 0;
+          } break;
+        }
+
+        switch (area) {
+
+        case 0 : // When mouse is out of image range.
+          mx = my = X = Y = Z = -1;
+          break;
+
+        case 1 : case 2 : case 3 : // When mouse is over the XY,XZ or YZ projections.
+          if (disp.button()&1 && phase<2 && clicked_area==area) { // When selection has been started (1st step).
+            if (_depth>1 && (X1!=X || Y1!=Y || Z1!=Z)) visu0.assign();
+            X1 = X; Y1 = Y; Z1 = Z;
+          }
+          if (!(disp.button()&1) && phase>=2 && clicked_area!=area) { // When selection is at 2nd step (for volumes).
+            switch (starting_area) {
+            case 1 : if (Z1!=Z) visu0.assign(); Z1 = Z; break;
+            case 2 : if (Y1!=Y) visu0.assign(); Y1 = Y; break;
+            case 3 : if (X1!=X) visu0.assign(); X1 = X; break;
+            }
+          }
+          if (disp.button()&2 && clicked_area==area) { // When moving through the image/volume.
+            if (phase) {
+              if (_depth>1 && (X1!=X || Y1!=Y || Z1!=Z)) visu0.assign();
+              X1 = X; Y1 = Y; Z1 = Z;
+            } else {
+              if (_depth>1 && (X0!=X || Y0!=Y || Z0!=Z)) visu0.assign();
+              X0 = X; Y0 = Y; Z0 = Z;
+            }
+          }
+          if (disp.button()&4) { // Reset positions.
+            oX = X = X0; oY = Y = Y0; oZ = Z = Z0; phase = area = clicked_area = starting_area = 0; visu0.assign();
+          }
+          if (disp.wheel()) { // When moving through the slices of the volume (with mouse wheel).
+            if (_depth>1 && !disp.is_keyCTRLLEFT() && !disp.is_keyCTRLRIGHT() && !disp.is_keySHIFTLEFT() && !disp.is_keySHIFTRIGHT() &&
+                !disp.is_keyALT() && !disp.is_keyALTGR()) {
+              switch (area) {
+              case 1 :
+                if (phase) Z = (Z1+=disp.wheel()); else Z = (Z0+=disp.wheel());
+                visu0.assign(); break;
+              case 2 :
+                if (phase) Y = (Y1+=disp.wheel()); else Y = (Y0+=disp.wheel());
+                visu0.assign(); break;
+              case 3 :
+                if (phase) X = (X1+=disp.wheel()); else X = (X0+=disp.wheel());
+                visu0.assign(); break;
+              }
+              disp.set_wheel();
+            } else key = ~0U;
+          }
+          if ((disp.button()&1)!=old_button) { // When left button has just been pressed or released.
+            switch (phase) {
+            case 0 :
+              if (area==clicked_area) {
+                X0 = X1 = X; Y0 = Y1 = Y; Z0 = Z1 = Z; starting_area = area; ++phase;
+              } break;
+            case 1 :
+              if (area==starting_area) {
+                X1 = X; Y1 = Y; Z1 = Z; ++phase;
+              } else if (!(disp.button()&1)) { oX = X = X0; oY = Y = Y0; oZ = Z = Z0; phase = 0; visu0.assign(); }
+              break;
+            case 2 : ++phase; break;
+            }
+            old_button = disp.button()&1;
+          }
+          break;
+
+        case 4 : // When mouse is over the 3d view.
+          if (is_view3d && points3d) {
+            X3d = mx - _width*disp.width()/(_width+(_depth>1?_depth:0));
+            Y3d = my - _height*disp.height()/(_height+(_depth>1?_depth:0));
+            if (oX3d<0) { oX3d = X3d; oY3d = Y3d; }
+            if ((disp.button()&3)==3) { pose3d.assign(); view3d.assign(); oX3d = oY3d = X3d = Y3d = -1; } // Left + right buttons : reset.
+            else if (disp.button()&1 && pose3d && (oX3d!=X3d || oY3d!=Y3d)) { // Left button : rotate.
+              const float
+                R = 0.45f*cimg::min(view3d._width,view3d._height),
+                R2 = R*R,
+                u0 = (float)(oX3d-view3d.width()/2),
+                v0 = (float)(oY3d-view3d.height()/2),
+                u1 = (float)(X3d-view3d.width()/2),
+                v1 = (float)(Y3d-view3d.height()/2),
+                n0 = (float)std::sqrt(u0*u0+v0*v0),
+                n1 = (float)std::sqrt(u1*u1+v1*v1),
+                nu0 = n0>R?(u0*R/n0):u0,
+                nv0 = n0>R?(v0*R/n0):v0,
+                nw0 = (float)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)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)std::sqrt(u*u+v*v+w*w),
+                alpha = (float)std::asin(n/R2);
+              pose3d.draw_image(CImg<floatT>::rotation_matrix(u,v,w,alpha)*pose3d.get_crop(0,0,2,2));
+              view3d.assign();
+            } else if (disp.button()&2 && pose3d && oY3d!=Y3d) {  // Right button : zoom.
+              pose3d(3,2)-=(oY3d - Y3d)*1.5f; view3d.assign();
+            }
+            if (disp.wheel()) { // Wheel : zoom
+              pose3d(3,2)-=disp.wheel()*15; view3d.assign(); disp.set_wheel();
+            }
+            if (disp.button()&4 && pose3d && (oX3d!=X3d || oY3d!=Y3d)) { // Middle button : shift.
+              pose3d(3,0)-=oX3d - X3d; pose3d(3,1)-=oY3d - Y3d; view3d.assign();
+            }
+            oX3d = X3d; oY3d = Y3d;
+          }
+          mx = my = X = Y = Z = -1;
+          break;
+        }
+
+        if (phase) {
+          if (!feature_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>=width()) X0 = width() - 1;
+        if (Y0<0) Y0 = 0; if (Y0>=height()) Y0 = height() - 1;
+        if (Z0<0) Z0 = 0; if (Z0>=depth()) Z0 = depth() - 1;
+        if (X1<1) X1 = 0; if (X1>=width()) X1 = width() - 1;
+        if (Y1<0) Y1 = 0; if (Y1>=height()) Y1 = height() - 1;
+        if (Z1<0) Z1 = 0; if (Z1>=depth()) Z1 = depth() - 1;
+
+        // Draw visualization image on the display
+        if (oX!=X || oY!=Y || oZ!=Z || !visu0 || (_depth>1 && !view3d)) {
+
+          if (!visu0) { // Create image of projected planes.
+            CImg<Tuchar> tmp, tmp0;
+            if (_depth!=1) {
+              tmp0 = get_projections2d(phase?X1:X0,phase?Y1:Y0,phase?Z1:Z0);
+              tmp = tmp0.get_channels(0,cimg::min(2,spectrum() - 1));
+            } else tmp = get_channels(0,cimg::min(2,spectrum() - 1));
+            switch (old_normalization) {
+            case 0 : tmp.move_to(visu0); break;
+            case 1 : tmp.normalize(0,255).move_to(visu0); break;
+            case 2 : {
+              const float m = disp._min, M = disp._max;
+              ((tmp-=m)*=255.0f/(M-m>0?M-m:1)).move_to(visu0);
+            }
+            case 3 :
+              if (cimg::type<T>::is_float()) (tmp.normalize(0,255)).move_to(visu0);
+              else {
+                const float m = (float)cimg::type<T>::min(), M = (float)cimg::type<T>::max();
+                ((tmp-=m)*=255.0f/(M-m)).move_to(visu0);
+              } break;
+            }
+            visu0.resize(disp);
+            view3d.assign();
+            points3d.assign();
+          }
+
+          if (is_view3d && _depth>1 && !view3d) { // Create 3d view for volumetric images.
+            const unsigned int
+              _x3d = (unsigned int)cimg::round((float)_width*visu0._width/(_width+_depth),1,1),
+              _y3d = (unsigned int)cimg::round((float)_height*visu0._height/(_height+_depth),1,1),
+              x3d = _x3d>=visu0._width?visu0._width-1:_x3d,
+              y3d = _y3d>=visu0._height?visu0._height-1:_y3d;
+            CImg<ucharT>(1,2,1,1,64,128).resize(visu0._width-x3d,visu0._height-y3d,1,visu0._spectrum,3).move_to(view3d);
+            if (!points3d) {
+              get_projections3d(primitives3d,colors3d,phase?X1:X0,phase?Y1:Y0,phase?Z1:Z0,true).move_to(points3d);
+              points3d.append(CImg<floatT>(8,3,1,1,
+                                           0,_width-1,_width-1,0,0,_width-1,_width-1,0,
+                                           0,0,_height-1,_height-1,0,0,_height-1,_height-1,
+                                           0,0,0,0,_depth-1,_depth-1,_depth-1,_depth-1),'x');
+              CImg<uintT>::vector(12,13).move_to(primitives3d); CImg<uintT>::vector(13,14).move_to(primitives3d);
+              CImg<uintT>::vector(14,15).move_to(primitives3d); CImg<uintT>::vector(15,12).move_to(primitives3d);
+              CImg<uintT>::vector(16,17).move_to(primitives3d); CImg<uintT>::vector(17,18).move_to(primitives3d);
+              CImg<uintT>::vector(18,19).move_to(primitives3d); CImg<uintT>::vector(19,16).move_to(primitives3d);
+              CImg<uintT>::vector(12,16).move_to(primitives3d); CImg<uintT>::vector(13,17).move_to(primitives3d);
+              CImg<uintT>::vector(14,18).move_to(primitives3d); CImg<uintT>::vector(15,19).move_to(primitives3d);
+              colors3d.insert(12,CImg<ucharT>::vector(255,255,255));
+              opacities3d.assign(primitives3d.width(),1,1,1,0.5f);
+              if (!phase) {
+                opacities3d[0] = opacities3d[1] = opacities3d[2] = 0.8f;
+                sel_primitives3d.assign();
+                sel_colors3d.assign();
+                sel_opacities3d.assign();
+              } else {
+                if (feature_type==2) {
+                  points3d.append(CImg<floatT>(8,3,1,1,
+                                               X0,X1,X1,X0,X0,X1,X1,X0,
+                                               Y0,Y0,Y1,Y1,Y0,Y0,Y1,Y1,
+                                               Z0,Z0,Z0,Z0,Z1,Z1,Z1,Z1),'x');
+                  sel_primitives3d.assign();
+                  CImg<uintT>::vector(20,21).move_to(sel_primitives3d); CImg<uintT>::vector(21,22).move_to(sel_primitives3d);
+                  CImg<uintT>::vector(22,23).move_to(sel_primitives3d); CImg<uintT>::vector(23,20).move_to(sel_primitives3d);
+                  CImg<uintT>::vector(24,25).move_to(sel_primitives3d); CImg<uintT>::vector(25,26).move_to(sel_primitives3d);
+                  CImg<uintT>::vector(26,27).move_to(sel_primitives3d); CImg<uintT>::vector(27,24).move_to(sel_primitives3d);
+                  CImg<uintT>::vector(20,24).move_to(sel_primitives3d); CImg<uintT>::vector(21,25).move_to(sel_primitives3d);
+                  CImg<uintT>::vector(22,26).move_to(sel_primitives3d); CImg<uintT>::vector(23,27).move_to(sel_primitives3d);
+                } else {
+                  points3d.append(CImg<floatT>(2,3,1,1,
+                                               X0,X1,
+                                               Y0,Y1,
+                                               Z0,Z1),'x');
+                  sel_primitives3d.assign(CImg<uintT>::vector(20,21));
+                }
+                sel_colors3d.assign(sel_primitives3d._width,CImg<ucharT>::vector(255,255,255));
+                sel_opacities3d.assign(sel_primitives3d._width,1,1,1,0.8f);
+              }
+              points3d.shift_object3d(-0.5f*_width,-0.5f*_height,-0.5f*_depth).resize_object3d();
+              points3d*=0.75f*cimg::min(view3d._width,view3d._height);
+            }
+
+            if (!pose3d) CImg<floatT>(4,3,1,1, 1,0,0,0, 0,1,0,0, 0,0,1,0).move_to(pose3d);
+            CImg<floatT> zbuffer3d(view3d._width,view3d._height,1,1,0);
+            const CImg<floatT> rotated_points3d = pose3d.get_crop(0,0,2,2)*points3d;
+            if (sel_primitives3d)
+              view3d.draw_object3d(pose3d(3,0) + 0.5f*view3d._width,
+                                   pose3d(3,1) + 0.5f*view3d._height,
+                                   pose3d(3,2),
+                                   rotated_points3d,sel_primitives3d,sel_colors3d,sel_opacities3d,
+                                   2,true,500,0,0,0,0,0,zbuffer3d);
+            view3d.draw_object3d(pose3d(3,0) + 0.5f*view3d._width,
+                                 pose3d(3,1) + 0.5f*view3d._height,
+                                 pose3d(3,2),
+                                 rotated_points3d,primitives3d,colors3d,opacities3d,
+                                 2,true,500,0,0,0,0,0,zbuffer3d);
+            visu0.draw_image(x3d,y3d,view3d);
+          }
+          visu = visu0;
+
+          const int d = (_depth>1)?_depth:0;
+          if (phase) switch (feature_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,background_color,0.9f,30,5,0x55555555).
+              draw_arrow(x0,y0,x1,y1,foreground_color,0.9f,30,5,0xAAAAAAAA);
+            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.9f,30,5,0x55555555).
+                draw_arrow(x0,zy0,x1,zy1,foreground_color,0.9f,30,5,0x55555555).
+                draw_arrow(zx0,y0,zx1,y1,foreground_color,0.9f,30,5,0xAAAAAAAA).
+                draw_arrow(x0,zy0,x1,zy1,foreground_color,0.9f,30,5,0xAAAAAAAA);
+            }
+          } 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,background_color,0.2f).draw_rectangle(x0,y0,x1,y1,foreground_color,0.6f,0x55555555);
+            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,background_color,0.2f).draw_rectangle(zx0,y0,zx1,y1,foreground_color,0.6f,0x55555555).
+                draw_rectangle(x0,zy0,x1,zy1,background_color,0.2f).draw_rectangle(x0,zy0,x1,zy1,foreground_color,0.6f,0x55555555);
+            }
+          } 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)cimg::abs(x1-x0),(float)cimg::abs(y1-y0),0,background_color,0.2f).
+              draw_ellipse(x0,y0,(float)cimg::abs(x1-x0),(float)cimg::abs(y1-y0),0,foreground_color,0.6f,0x55555555).
+              draw_point(x0,y0,foreground_color,0.6f);
+            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)cimg::abs(zx1-zx0),(float)cimg::abs(y1-y0),0,background_color,0.2f).
+                draw_ellipse(zx0,y0,(float)cimg::abs(zx1-zx0),(float)cimg::abs(y1-y0),0,foreground_color,0.6f,0x55555555).
+                draw_point(zx0,y0,foreground_color,0.6f).
+                draw_ellipse(x0,zy0,(float)cimg::abs(x1-x0),(float)cimg::abs(zy1-zy0),0,background_color,0.2f).
+                draw_ellipse(x0,zy0,(float)cimg::abs(x1-x0),(float)cimg::abs(zy1-zy0),0,foreground_color,0.6f,0x55555555).
+                draw_point(x0,zy0,foreground_color,0.6f);
+            }
+          } 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,background_color,0.2f).
+                                        draw_rectangle(x0,y0,x1,y1,foreground_color,0.6f,~0U);
+          }
+
+          if (my>=0 && my<13) text_down = true; else if (my>=visu.height()-13) text_down = false;
+          if (!feature_type || !phase) {
+            if (X>=0 && Y>=0 && Z>=0 && X<width() && Y<height() && Z<depth()) {
+              if (_depth>1) cimg_snprintf(text,sizeof(text)," Point (%d,%d,%d) = [ ",origX+X,origY+Y,origZ+Z);
+              else cimg_snprintf(text,sizeof(text)," Point (%d,%d) = [ ",origX+X,origY+Y);
+              char *ctext = text + std::strlen(text), *const ltext = text + 512;
+              for (unsigned int c = 0; c<_spectrum && ctext<ltext; ++c) {
+                cimg_snprintf(ctext,sizeof(text)/2,cimg::type<T>::format(),cimg::type<T>::format((*this)(X,Y,Z,c)));
+                ctext = text + std::strlen(text);
+                *(ctext++) = ' '; *ctext = 0;
+              }
+              std::strcpy(text + std::strlen(text),"] ");
+            }
+          } else switch (feature_type) {
+          case 1 : {
+            const double dX = (double)(X0 - X1), dY = (double)(Y0 - Y1), dZ = (double)(Z0 - Z1), norm = std::sqrt(dX*dX+dY*dY+dZ*dZ);
+            if (_depth>1) cimg_snprintf(text,sizeof(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_snprintf(text,sizeof(text)," Vect (%d,%d)-(%d,%d), Norm = %g ",
+                               origX+X0,origY+Y0,origX+X1,origY+Y1,norm);
+          } break;
+          case 2 :
+            if (_depth>1) cimg_snprintf(text,sizeof(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_snprintf(text,sizeof(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_snprintf(text,sizeof(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_snprintf(text,sizeof(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.height()-13:0,text,foreground_color,background_color,0.7f,13);
+          disp.display(visu).wait();
+        } else if (!shape_selected) disp.wait();
+        if (disp.is_resized()) { disp.resize(false)._is_resized = false; old_is_resized = true; visu0.assign(); }
+      }
+
+      // Return result
+      CImg<intT> res(1,feature_type==0?3: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 (feature_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 (feature_type) {
+        case 1 : case 2 : res[0] = X0; res[1] = Y0; res[2] = Z0; res[3] = X1; res[4] = Y1; res[5] = Z1; break;
+        case 3 : res[3] = cimg::abs(X1-X0); res[4] = cimg::abs(Y1-Y0); res[5] = cimg::abs(Z1-Z0);  // keep no break here !
+        default : res[0] = X0; res[1] = Y0; res[2] = Z0;
+        }
+      }
+      disp.set_button();
+      disp._normalization = old_normalization;
+      disp._is_resized = old_is_resized;
+      if (key!=~0U) disp.set_key(key);
+      return res;
+    }
+
+    //! 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_instance
+                                    "select_graph() : Empty instance.",
+                                    cimg_instance);
+      if (!disp) disp.assign(cimg_fitscreen(640,480,1),0,0).set_title("CImg<%s>",pixel_type());
+      const unsigned int siz = _width*_height*_depth, old_normalization = disp.normalization();
+      disp.show().set_button().set_wheel()._normalization = 0;
+
+      double nymin = ymin, nymax = ymax, nxmin = xmin, nxmax = xmax;
+      if (nymin==nymax) { nymin = (Tfloat)min_max(nymax); const double dy = nymax - nymin; nymin-=dy/20; nymax+=dy/20; }
+      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!=_spectrum) {
+        odimv = _spectrum;
+        palette = CImg<ucharT>(3,_spectrum,1,1,120).noise(70,1);
+        if (_spectrum==1) { palette[0] = palette[1] = 120; palette[2] = 200; }
+        else {
+          palette(0,0) = 220; palette(1,0) = 10; palette(2,0) = 10;
+          if (_spectrum>1) { palette(0,1) = 10; palette(1,1) = 220; palette(2,1) = 10; }
+          if (_spectrum>2) { palette(0,2) = 10; palette(1,2) = 10; palette(2,2) = 220; }
+        }
+      }
+
+      CImg<ucharT> visu0, visu, graph, text, axes;
+      int x0 = -1, x1 = -1, y0 = -1, y1 = -1, omouse_x = -2, omouse_y = -2;
+      const unsigned int one = plot_type==3?0:1;
+      unsigned int okey = 0, obutton = 0;
+      char message[1024] = { 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.width(),disp.height(),1,3,220);
+          const int gdimx = disp.width() - 32, gdimy = disp.height() - 32;
+          if (gdimx>0 && gdimy>0) {
+            graph.assign(gdimx,gdimy,1,3,255);
+            if (siz<32) { if (siz>1) graph.draw_grid(gdimx/(float)(siz - one),gdimy/(float)(siz - one),0,0,false,true,black,0.2f,0x33333333,0x33333333); }
+            else graph.draw_grid(-10,-10,0,0,false,true,black,0.2f,0x33333333,0x33333333);
+            cimg_forC(*this,c) graph.draw_graph(get_shared_channel(c),&palette(0,c),(plot_type!=3 || _spectrum==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)std::pow(10.0,(int)std::log10(dx?dx:1)-2.0),
+              py = (float)std::pow(10.0,(int)std::log10(dy?dy:1)-2.0);
+            const CImg<Tdouble>
+              seqx = dx<=0?CImg<Tdouble>::vector(nxmin):CImg<Tdouble>::sequence(1 + gdimx/60,nxmin,one?nxmax:nxmin+(nxmax-nxmin)*(siz+1)/siz).round(px),
+              seqy = CImg<Tdouble>::sequence(1 + gdimy/60,nymax,nymin).round(py);
+
+            const bool allow_zero = (nxmin*nxmax>0) || (nymin*nymax>0);
+            axes.draw_axes(seqx,seqy,white,1,~0U,~0U,13,allow_zero);
+            if (nymin>0) axes.draw_axis(seqx,gdimy-1,gray,1,~0U,13,allow_zero);
+            if (nymax<0) axes.draw_axis(seqx,0,gray,1,~0U,13,allow_zero);
+            if (nxmin>0) axes.draw_axis(0,seqy,gray,1,~0U,13,allow_zero);
+            if (nxmax<0) axes.draw_axis(gdimx-1,seqy,gray,~0U,13,allow_zero);
+
+            cimg_for3x3(axes,x,y,0,0,I,unsigned char)
+              if (Icc) {
+                if (Icc==255) cimg_forC(graph,c) graph(x,y,c) = 0;
+                else cimg_forC(graph,c) graph(x,y,c) = (unsigned char)(2*graph(x,y,c)/3);
+              }
+              else if (Ipc || Inc || Icp || Icn || Ipp || Inn || Ipn || Inp) cimg_forC(graph,c) graph(x,y,c) = (graph(x,y,c)+511)/3;
+
+            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,13).resize(-100,-100,1,3);
+          visu0.draw_image((visu0.width()-text.width())/2,visu0.height()-14,~text);
+          text.assign().draw_text(0,0,labely?labely:"Y-axis",white,ngray,1,13).rotate(-90).resize(-100,-100,1,3);
+          visu0.draw_image(1,(visu0.height()-text.height())/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.width()-32)/cimg::max(1U,siz-one),
+              sx1 = 15 + (nx1+1)*(visu.width()-32)/cimg::max(1U,siz-one),
+              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.height()-17,gray,0.5f).
+                   draw_line(sx0,16,sx0,visu.height()-17,black,0.5f,0xCCCCCCCCU).
+                   draw_line(sx1,16,sx1,visu.height()-17,black,0.5f,0xCCCCCCCCU);
+          }
+          if (mouse_x>=16 && mouse_y>=16 && mouse_x<visu.width()-16 && mouse_y<visu.height()-16) {
+            if (graph) visu.draw_line(mouse_x,16,mouse_x,visu.height()-17,black,0.5f,0x55555555U);
+            const unsigned int x = (unsigned int)cimg::round((mouse_x-16.0f)*(siz-one)/(disp.width()-32),1,one?0:-1);
+            const double cx = nxmin + x*(nxmax-nxmin)/cimg::max(1U,siz-1);
+            if (_spectrum>=7)
+              cimg_snprintf(message,sizeof(message),"Value[%u:%g] = ( %g %g %g ... %g %g %g )",x,cx,
+                            (double)(*this)(x,0,0,0),(double)(*this)(x,0,0,1),(double)(*this)(x,0,0,2),
+                            (double)(*this)(x,0,0,_spectrum-4),(double)(*this)(x,0,0,_spectrum-3),(double)(*this)(x,0,0,_spectrum-1));
+            else {
+              cimg_snprintf(message,sizeof(message),"Value[%u:%g] = ( ",x,cx);
+              cimg_forC(*this,c) std::sprintf(message + std::strlen(message),"%g ",(double)(*this)(x,0,0,c));
+              std::sprintf(message + std::strlen(message),")");
+            }
+            if (x0>=0 && x1>=0) {
+              const unsigned 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)/cimg::max(1U,siz-1),
+                cx1 = nxmin + (nx1+one)*(nxmax-nxmin)/cimg::max(1U,siz-1),
+                cy0 = nymax - ny0*(nymax-nymin)/(visu._height-32),
+                cy1 = nymax - ny1*(nymax-nymin)/(visu._height-32);
+              if (y0>=0 && y1>=0)
+                std::sprintf(message + std::strlen(message)," - Range ( %u:%g, %g ) - ( %u:%g, %g )",x0,cx0,cy0,x1+one,cx1,cy1);
+              else
+                std::sprintf(message + std::strlen(message)," - Range [ %u:%g - %u:%g ]",x0,cx0,x1+one,cx1);
+            }
+            text.assign().draw_text(0,0,message,white,ngray,1,13).resize(-100,-100,1,3);
+            visu.draw_image((visu.width()-text.width())/2,1,~text);
+          }
+          visu.display(disp);
+        }
+
+        // Test keys.
+        switch (okey = key) {
+#if cimg_OS!=2
+        case cimg::keyCTRLRIGHT : case cimg::keySHIFTRIGHT :
+#endif
+        case cimg::keyCTRLLEFT : case cimg::keySHIFTLEFT : okey = 0; break;
+        case cimg::keyD : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) {
+          disp.set_fullscreen(false).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.set_key(key,false); okey = 0;
+        } break;
+        case cimg::keyC : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) {
+          disp.set_fullscreen(false).resize(cimg_fitscreen(2*disp.width()/3,2*disp.height()/3,1),false)._is_resized = true;
+          disp.set_key(key,false); okey = 0;
+        } break;
+        case cimg::keyR : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) {
+          disp.set_fullscreen(false).resize(cimg_fitscreen(640,480,1),false)._is_resized = true;
+          disp.set_key(key,false); okey = 0;
+        } break;
+        case cimg::keyF : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) {
+          disp.resize(disp.screen_width(),disp.screen_height(),false).toggle_fullscreen()._is_resized = true;
+          disp.set_key(key,false); okey = 0;
+        } break;
+        case cimg::keyS : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) {
+          static unsigned int snap_number = 0;
+          if (visu || visu0) {
+            CImg<ucharT> &screen = visu?visu:visu0;
+            char filename[32] = { 0 };
+            std::FILE *file;
+            do {
+              cimg_snprintf(filename,sizeof(filename),cimg_appname "_%.4u.bmp",snap_number++);
+              if ((file=std::fopen(filename,"r"))!=0) cimg::fclose(file);
+            } while (file);
+            (+screen).draw_text(0,0," Saving snapshot... ",black,gray,1,13).display(disp);
+            screen.save(filename);
+            screen.draw_text(0,0," Snapshot '%s' saved. ",black,gray,1,13,filename).display(disp);
+          }
+          disp.set_key(key,false); okey = 0;
+        } break;
+        case cimg::keyO : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) {
+            static unsigned int snap_number = 0;
+            if (visu || visu0) {
+              CImg<ucharT> &screen = visu?visu:visu0;
+              char filename[32] = { 0 };
+              std::FILE *file;
+              do {
+#ifdef cimg_use_zlib
+                cimg_snprintf(filename,sizeof(filename),cimg_appname "_%.4u.cimgz",snap_number++);
+#else
+                cimg_snprintf(filename,sizeof(filename),cimg_appname "_%.4u.cimg",snap_number++);
+#endif
+                if ((file=std::fopen(filename,"r"))!=0) cimg::fclose(file);
+              } while (file);
+              (+screen).draw_text(0,0," Saving instance... ",black,gray,1,13).display(disp);
+              save(filename);
+              screen.draw_text(0,0," Instance '%s' saved. ",black,gray,1,13,filename).display(disp);
+            }
+            disp.set_key(key,false); 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)(siz-one)/(disp.width()-32),
+              cx = mx<0?0:(mx>=(int)(siz-one)?(int)(siz-1-one):mx),
+              my = mouse_y - 16,
+              cy = my<=0?0:(my>=(disp.height()-32)?(disp.height()-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 = x1>=0?cx:-1; 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 = old_normalization;
+      if (x1>=0 && x1<x0) cimg::swap(x0,x1);
+      if (y1<y0) cimg::swap(y0,y1);
+      disp.set_key(okey);
+      return CImg<intT>(4,1,1,1,x0,y0,x1>=0?x1+(int)one:-1,y1);
+    }
+
+    //! 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_instance
+                                    "load() : Specified filename is (null).",
+                                    cimg_instance);
+
+      if (!cimg::strncasecmp(filename,"http://",7) || !cimg::strncasecmp(filename,"https://",8)) {
+        char filename_local[1024] = { 0 };
+        load(cimg::load_network_external(filename,filename_local));
+        std::remove(filename_local);
+        return *this;
+      }
+
+      const char *const ext = cimg::split_filename(filename);
+      const unsigned int omode = cimg::exception_mode();
+      cimg::exception_mode() = 0;
+      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);
+        else if (!cimg::strcasecmp(ext,"dlm") ||
+                 !cimg::strcasecmp(ext,"txt")) load_dlm(filename);
+
+        // 2d binary formats
+        else if (!cimg::strcasecmp(ext,"bmp")) load_bmp(filename);
+        else if (!cimg::strcasecmp(ext,"jpg") ||
+                 !cimg::strcasecmp(ext,"jpeg") ||
+                 !cimg::strcasecmp(ext,"jpe") ||
+                 !cimg::strcasecmp(ext,"jfif") ||
+                 !cimg::strcasecmp(ext,"jif")) load_jpeg(filename);
+        else if (!cimg::strcasecmp(ext,"png")) load_png(filename);
+        else if (!cimg::strcasecmp(ext,"ppm") ||
+                 !cimg::strcasecmp(ext,"pgm") ||
+                 !cimg::strcasecmp(ext,"pnm") ||
+                 !cimg::strcasecmp(ext,"pbm") ||
+                 !cimg::strcasecmp(ext,"pnk")) load_pnm(filename);
+        else if (!cimg::strcasecmp(ext,"pfm")) load_pfm(filename);
+        else if (!cimg::strcasecmp(ext,"tif") ||
+                 !cimg::strcasecmp(ext,"tiff")) load_tiff(filename);
+        else if (!cimg::strcasecmp(ext,"exr")) load_exr(filename);
+        else 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
+        else if (!cimg::strcasecmp(ext,"dcm") ||
+                 !cimg::strcasecmp(ext,"dicom")) load_medcon_external(filename);
+        else if (!cimg::strcasecmp(ext,"hdr") ||
+                 !cimg::strcasecmp(ext,"nii")) load_analyze(filename);
+        else if (!cimg::strcasecmp(ext,"par") ||
+                 !cimg::strcasecmp(ext,"rec")) load_parrec(filename);
+        else if (!cimg::strcasecmp(ext,"mnc")) load_minc2(filename);
+        else if (!cimg::strcasecmp(ext,"inr")) load_inr(filename);
+        else if (!cimg::strcasecmp(ext,"pan")) load_pandore(filename);
+        else if (!cimg::strcasecmp(ext,"cimg") ||
+                 !cimg::strcasecmp(ext,"cimgz") ||
+                 !*ext)  return load_cimg(filename);
+
+        // Archive files
+        else if (!cimg::strcasecmp(ext,"gz")) load_gzip_external(filename);
+
+        // Image sequences
+        else if (!cimg::strcasecmp(ext,"avi") ||
+                 !cimg::strcasecmp(ext,"mov") ||
+                 !cimg::strcasecmp(ext,"asf") ||
+                 !cimg::strcasecmp(ext,"divx") ||
+                 !cimg::strcasecmp(ext,"flv") ||
+                 !cimg::strcasecmp(ext,"mpg") ||
+                 !cimg::strcasecmp(ext,"m1v") ||
+                 !cimg::strcasecmp(ext,"m2v") ||
+                 !cimg::strcasecmp(ext,"m4v") ||
+                 !cimg::strcasecmp(ext,"mjp") ||
+                 !cimg::strcasecmp(ext,"mkv") ||
+                 !cimg::strcasecmp(ext,"mpe") ||
+                 !cimg::strcasecmp(ext,"movie") ||
+                 !cimg::strcasecmp(ext,"ogm") ||
+                 !cimg::strcasecmp(ext,"ogg") ||
+                 !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);
+        else throw CImgIOException("CImg<%s>::load()",
+                                   pixel_type());
+      } catch (CImgIOException&) {
+        std::FILE *file = 0;
+        try {
+          file = cimg::fopen(filename,"rb");
+        } catch (CImgIOException&) {
+          cimg::exception_mode() = omode;
+          throw CImgIOException(_cimg_instance
+                                "load() : Failed to open file '%s'.",
+                                cimg_instance,
+                                filename);
+        }
+        try {
+          const char *const f_type = cimg::file_type(file,filename);
+          std::fclose(file);
+          if (!cimg::strcasecmp(f_type,"pnm")) load_pnm(filename);
+          else if (!cimg::strcasecmp(f_type,"pfm")) load_pfm(filename);
+          else if (!cimg::strcasecmp(f_type,"bmp")) load_bmp(filename);
+          else if (!cimg::strcasecmp(f_type,"jpg")) load_jpeg(filename);
+          else if (!cimg::strcasecmp(f_type,"pan")) load_pandore(filename);
+          else if (!cimg::strcasecmp(f_type,"png")) load_png(filename);
+          else if (!cimg::strcasecmp(f_type,"tif")) load_tiff(filename);
+          else if (!cimg::strcasecmp(f_type,"inr")) load_inr(filename);
+          else if (!cimg::strcasecmp(f_type,"dcm")) load_medcon_external(filename);
+          else throw CImgIOException("CImg<%s>::load()",
+                                     pixel_type());
+        } catch (CImgIOException&) {
+          try {
+            load_other(filename);
+          } catch (CImgIOException&) {
+            cimg::exception_mode() = omode;
+            throw CImgIOException(_cimg_instance
+                                  "load() : Failed to recognize format of file '%s'.",
+                                  cimg_instance,
+                                  filename);
+          }
+        }
+      }
+      cimg::exception_mode() = omode;
+      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(std::FILE *const file) {
+      return _load_ascii(file,0);
+    }
+
+    static CImg<T> get_load_ascii(std::FILE *const file) {
+      return CImg<T>().load_ascii(file);
+    }
+
+    CImg<T>& _load_ascii(std::FILE *const file, const char *const filename) {
+      if (!file && !filename)
+        throw CImgArgumentException(_cimg_instance
+                                    "load_ascii() : Specified filename is (null).",
+                                    cimg_instance);
+
+      std::FILE *const nfile = file?file:cimg::fopen(filename,"rb");
+      char line[256] = { 0 };
+      int err = std::fscanf(nfile,"%255[^\n]",line);
+      unsigned int off, dx = 0, dy = 1, dz = 1, dc = 1;
+      std::sscanf(line,"%u%*c%u%*c%u%*c%u",&dx,&dy,&dz,&dc);
+      err = std::fscanf(nfile,"%*[^0-9.eE+-]");
+      if (!dx || !dy || !dz || !dc) {
+        if (!file) cimg::fclose(nfile);
+        throw CImgIOException(_cimg_instance
+                              "load_ascii() : Invalid ASCII header in file '%s', image dimensions are set to (%u,%u,%u,%u).",
+                              cimg_instance,
+                              filename?filename:"(FILE*)",dx,dy,dz,dc);
+      }
+      assign(dx,dy,dz,dc);
+      const unsigned int siz = size();
+      double val;
+      T *ptr = _data;
+      for (err = 1, off = 0; off<siz && err==1; ++off) {
+        err = std::fscanf(nfile,"%lf%*[^0-9.eE+-]",&val);
+        *(ptr++) = (T)val;
+      }
+      if (err!=1)
+        cimg::warn(_cimg_instance
+                   "load_ascii() : Only %u/%u values read from file '%s'.",
+                   cimg_instance,
+                   off-1,siz,filename?filename:"(FILE*)");
+
+      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(std::FILE *const file) {
+      return _load_dlm(file,0);
+    }
+
+    static CImg<T> get_load_dlm(std::FILE *const file) {
+      return CImg<T>().load_dlm(file);
+    }
+
+    CImg<T>& _load_dlm(std::FILE *const file, const char *const filename) {
+      if (!file && !filename)
+        throw CImgArgumentException(_cimg_instance
+                                    "load_dlm() : Specified filename is (null).",
+                                    cimg_instance);
+
+      std::FILE *const nfile = file?file:cimg::fopen(filename,"r");
+      char delimiter[256] = { 0 }, tmp[256] = { 0 };
+      unsigned int cdx = 0, dx = 0, dy = 0;
+      int err = 0;
+      double val;
+      assign(256,256);
+      while ((err = std::fscanf(nfile,"%lf%255[^0-9.+-]",&val,delimiter))>0) {
+        if (err>0) (*this)(cdx++,dy) = (T)val;
+        if (cdx>=_width) resize(3*_width/2,_height,1,1,0);
+        char c = 0;
+        if (!std::sscanf(delimiter,"%255[^\n]%c",tmp,&c) || c=='\n') {
+          dx = cimg::max(cdx,dx);
+          if (++dy>=_height) resize(_width,3*_height/2,1,1,0);
+          cdx = 0;
+        }
+      }
+      if (cdx && err==1) { dx = cdx; ++dy; }
+      if (!dx || !dy) {
+        if (!file) cimg::fclose(nfile);
+        throw CImgIOException(_cimg_instance
+                              "load_dlm() : Invalid DLM file '%s'.",
+                              cimg_instance,
+                              filename?filename:"(FILE*)");
+      }
+      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(std::FILE *const file) {
+      return _load_bmp(file,0);
+    }
+
+    static CImg<T> get_load_bmp(std::FILE *const file) {
+      return CImg<T>().load_bmp(file);
+    }
+
+    CImg<T>& _load_bmp(std::FILE *const file, const char *const filename) {
+      if (!file && !filename)
+        throw CImgArgumentException(_cimg_instance
+                                    "load_bmp() : Specified filename is (null).",
+                                    cimg_instance);
+
+      std::FILE *const nfile = file?file:cimg::fopen(filename,"rb");
+      unsigned char header[64] = { 0 };
+      cimg::fread(header,54,nfile);
+      if (*header!='B' || header[1]!='M') {
+        if (!file) cimg::fclose(nfile);
+        throw CImgIOException(_cimg_instance
+                              "load_bmp() : Invalid BMP file '%s'.",
+                              cimg_instance,
+                              filename?filename:"(FILE*)");
+      }
+
+      // 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);
+
+      if (!file_size || file_size==offset) {
+        std::fseek(nfile,0,SEEK_END);
+        file_size = (int)std::ftell(nfile);
+        std::fseek(nfile,54,SEEK_SET);
+      }
+
+      const int
+        cimg_iobuffer = 12*1024*1024,
+        dx_bytes = (bpp==1)?(dx/8+(dx%8?1:0)):((bpp==4)?(dx/2+(dx%2?1:0)):(dx*bpp/8)),
+        align_bytes = (4-dx_bytes%4)%4,
+        buf_size = cimg::min(cimg::abs(dy)*(dx_bytes + align_bytes),file_size - offset);
+
+      CImg<intT> palette;
+      if (bpp<16) { if (!nb_colors) nb_colors = 1<<bpp; } else nb_colors = 0;
+      if (nb_colors) { palette.assign(nb_colors); cimg::fread(palette._data,nb_colors,nfile); }
+      const int xoffset = offset - 54 - 4*nb_colors;
+      if (xoffset>0) std::fseek(nfile,xoffset,SEEK_CUR);
+
+      CImg<ucharT> buffer;
+      if (buf_size<cimg_iobuffer) { buffer.assign(buf_size); cimg::fread(buffer._data,buf_size,nfile); }
+      else buffer.assign(dx_bytes + align_bytes);
+      unsigned char *ptrs = buffer;
+
+      // Decompress buffer (if necessary)
+      if (compression) {
+        if (file)
+          throw CImgIOException(_cimg_instance
+                                "load_bmp() : Unable to load compressed data from '(*FILE)' inputs.",
+                                cimg_instance);
+        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) {
+          if (buf_size>=cimg_iobuffer) { cimg::fread(ptrs=buffer._data,dx_bytes,nfile); std::fseek(nfile,align_bytes,SEEK_CUR); }
+          unsigned char mask = 0x80, val = 0;
+          cimg_forX(*this,x) {
+            if (mask==0x80) val = *(ptrs++);
+            const unsigned char *col = (unsigned char*)(palette._data + (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_bytes;
+        }
+      } break;
+      case 4 : { // 16 colors
+        for (int y = height()-1; y>=0; --y) {
+          if (buf_size>=cimg_iobuffer) { cimg::fread(ptrs=buffer._data,dx_bytes,nfile); std::fseek(nfile,align_bytes,SEEK_CUR); }
+          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));
+            const unsigned char *col = (unsigned char*)(palette._data + 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_bytes;
+        }
+      } break;
+      case 8 : { //  256 colors
+        for (int y = height()-1; y>=0; --y) {
+          if (buf_size>=cimg_iobuffer) { cimg::fread(ptrs=buffer._data,dx_bytes,nfile); std::fseek(nfile,align_bytes,SEEK_CUR); }
+          cimg_forX(*this,x) {
+            const unsigned char *col = (unsigned char*)(palette._data + *(ptrs++));
+            (*this)(x,y,2) = (T)*(col++);
+            (*this)(x,y,1) = (T)*(col++);
+            (*this)(x,y,0) = (T)*(col++);
+          }
+          ptrs+=align_bytes;
+        }
+      } break;
+      case 16 : { // 16 bits colors
+        for (int y = height()-1; y>=0; --y) {
+          if (buf_size>=cimg_iobuffer) { cimg::fread(ptrs=buffer._data,dx_bytes,nfile); std::fseek(nfile,align_bytes,SEEK_CUR); }
+          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_bytes;
+        }
+      } break;
+      case 24 : { // 24 bits colors
+        for (int y = height()-1; y>=0; --y) {
+          if (buf_size>=cimg_iobuffer) { cimg::fread(ptrs=buffer._data,dx_bytes,nfile); std::fseek(nfile,align_bytes,SEEK_CUR); }
+          cimg_forX(*this,x) {
+            (*this)(x,y,2) = (T)*(ptrs++);
+            (*this)(x,y,1) = (T)*(ptrs++);
+            (*this)(x,y,0) = (T)*(ptrs++);
+          }
+          ptrs+=align_bytes;
+        }
+      } break;
+      case 32 : { // 32 bits colors
+        for (int y = height()-1; y>=0; --y) {
+          if (buf_size>=cimg_iobuffer) { cimg::fread(ptrs=buffer._data,dx_bytes,nfile); std::fseek(nfile,align_bytes,SEEK_CUR); }
+          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_bytes;
+        }
+      } break;
+      }
+      if (dy<0) mirror('y');
+      if (!file) cimg::fclose(nfile);
+      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(std::FILE *const file) {
+      return _load_jpeg(file,0);
+    }
+
+    static CImg<T> get_load_jpeg(std::FILE *const file) {
+      return CImg<T>().load_jpeg(file);
+    }
+
+    // Custom error handler for libjpeg.
+#ifdef cimg_use_jpeg
+    struct _cimg_error_mgr {
+      struct jpeg_error_mgr original;
+      jmp_buf setjmp_buffer;
+      char message[JMSG_LENGTH_MAX];
+    };
+
+    typedef struct _cimg_error_mgr *_cimg_error_ptr;
+
+    METHODDEF(void) _cimg_jpeg_error_exit(j_common_ptr cinfo) {
+      _cimg_error_ptr c_err = (_cimg_error_ptr) cinfo->err;  // Return control to the setjmp point
+      (*cinfo->err->format_message)(cinfo,c_err->message);
+      jpeg_destroy(cinfo);  // Clean memory and temp files.
+      longjmp(c_err->setjmp_buffer,1);
+    }
+#endif
+
+    CImg<T>& _load_jpeg(std::FILE *const file, const char *const filename) {
+      if (!file && !filename)
+        throw CImgArgumentException(_cimg_instance
+                                    "load_jpeg() : Specified filename is (null).",
+                                    cimg_instance);
+
+#ifndef cimg_use_jpeg
+      if (file)
+        throw CImgIOException(_cimg_instance
+                              "load_jpeg() : Unable to load data from '(FILE*)' unless libjpeg is enabled.",
+                              cimg_instance);
+      else return load_other(filename);
+#else
+
+      struct jpeg_decompress_struct cinfo;
+      struct _cimg_error_mgr jerr;
+      cinfo.err = jpeg_std_error(&jerr.original);
+      jerr.original.error_exit = _cimg_jpeg_error_exit;
+
+      if (setjmp(jerr.setjmp_buffer)) { // JPEG error
+        throw CImgIOException(_cimg_instance
+                             "load_jpeg() : Error message returned by libjpeg : %s.",
+                             cimg_instance,jerr.message);
+      }
+
+      std::FILE *const nfile = file?file:cimg::fopen(filename,"rb");
+      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) {
+        if (!file) return load_other(filename);
+        else
+          throw CImgIOException(_cimg_instance
+                                "load_jpeg() : Failed to load JPEG data from file '%s'.",
+                                cimg_instance,filename?filename:"(FILE*)");
+      }
+      CImg<ucharT> buffer(cinfo.output_width*cinfo.output_components);
+      JSAMPROW row_pointer[1];
+      assign(cinfo.output_width,cinfo.output_height,1,cinfo.output_components);
+      T *ptr_r = _data, *ptr_g = _data + _width*_height, *ptr_b = _data + 2*_width*_height, *ptr_a = _data + 3*_width*_height;
+      while (cinfo.output_scanline<cinfo.output_height) {
+        *row_pointer = buffer._data;
+        if (jpeg_read_scanlines(&cinfo,row_pointer,1)!=1) {
+          cimg::warn(_cimg_instance
+                     "load_jpeg() : Incomplete data in file '%s'.",
+                     cimg_instance,filename?filename:"(FILE*)");
+          break;
+        }
+        const unsigned char *ptrs = buffer._data;
+        switch (_spectrum) {
+        case 1 : {
+          cimg_forX(*this,x) *(ptr_r++) = (T)*(ptrs++);
+        } break;
+        case 3 : {
+          cimg_forX(*this,x) {
+            *(ptr_r++) = (T)*(ptrs++);
+            *(ptr_g++) = (T)*(ptrs++);
+            *(ptr_b++) = (T)*(ptrs++);
+          }
+        } break;
+        case 4 : {
+          cimg_forX(*this,x) {
+            *(ptr_r++) = (T)*(ptrs++);
+            *(ptr_g++) = (T)*(ptrs++);
+            *(ptr_b++) = (T)*(ptrs++);
+            *(ptr_a++) = (T)*(ptrs++);
+          }
+        } break;
+        }
+      }
+      jpeg_finish_decompress(&cinfo);
+      jpeg_destroy_decompress(&cinfo);
+      if (!file) cimg::fclose(nfile);
+      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_instance
+                                    "load_magick() : Specified filename is (null).",
+                                    cimg_instance);
+
+#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 *ptr_r = data(0,0,0,0), *ptr_g = data(0,0,0,1), *ptr_b = data(0,0,0,2), *ptr_a = data(0,0,0,3);
+        Magick::PixelPacket *pixels = image.getPixels(0,0,W,H);
+        for (unsigned int off = W*H; off; --off) {
+          *(ptr_r++) = (T)(pixels->red);
+          *(ptr_g++) = (T)(pixels->green);
+          *(ptr_b++) = (T)(pixels->blue);
+          *(ptr_a++) = (T)(pixels->opacity);
+          ++pixels;
+        }
+      } break;
+      case Magick::PaletteType :
+      case Magick::TrueColorType : {
+        assign(W,H,1,3);
+        T *ptr_r = data(0,0,0,0), *ptr_g = data(0,0,0,1), *ptr_b = data(0,0,0,2);
+        Magick::PixelPacket *pixels = image.getPixels(0,0,W,H);
+        for (unsigned int off = W*H; off; --off) {
+          *(ptr_r++) = (T)(pixels->red);
+          *(ptr_g++) = (T)(pixels->green);
+          *(ptr_b++) = (T)(pixels->blue);
+          ++pixels;
+        }
+      } break;
+      case Magick::GrayscaleMatteType : {
+        assign(W,H,1,2);
+        T *ptr_r = data(0,0,0,0), *ptr_a = data(0,0,0,1);
+        Magick::PixelPacket *pixels = image.getPixels(0,0,W,H);
+        for (unsigned int off = W*H; off; --off) {
+          *(ptr_r++) = (T)(pixels->red);
+          *(ptr_a++) = (T)(pixels->opacity);
+          ++pixels;
+        }
+      } break;
+      default : {
+        assign(W,H,1,1);
+        T *ptr_r = data(0,0,0,0);
+        Magick::PixelPacket *pixels = image.getPixels(0,0,W,H);
+        for (unsigned int off = W*H; off; --off) {
+          *(ptr_r++) = (T)(pixels->red);
+          ++pixels;
+        }
+      }
+      }
+#else
+      throw CImgIOException(_cimg_instance
+                            "load_magick() : Unable to load file '%s' unless libMagick++ is enabled.",
+                            cimg_instance,
+                            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(std::FILE *const file) {
+      return _load_png(file,0);
+    }
+
+    static CImg<T> get_load_png(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(std::FILE *const file, const char *const filename) {
+      if (!file && !filename)
+        throw CImgArgumentException(_cimg_instance
+                                    "load_png() : Specified filename is (null).",
+                                    cimg_instance);
+
+#ifndef cimg_use_png
+      if (file)
+        throw CImgIOException(_cimg_instance
+                              "load_png() : Unable to load data from '(FILE*)' unless libpng is enabled.",
+                              cimg_instance);
+
+      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'.
+      std::FILE *volatile nfile = file?file:cimg::fopen(nfilename,"rb");
+
+      unsigned char pngCheck[8] = { 0 };
+      cimg::fread(pngCheck,8,(std::FILE*)nfile);
+      if (png_sig_cmp(pngCheck,0,8)) {
+        if (!file) cimg::fclose(nfile);
+        throw CImgIOException(_cimg_instance
+                              "load_png() : Invalid PNG file '%s'.",
+                              cimg_instance,
+                              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_instance
+                              "load_png() : Failed to initialize 'png_ptr' structure for file '%s'.",
+                              cimg_instance,
+                              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_instance
+                              "load_png() : Failed to initialize 'info_ptr' structure for file '%s'.",
+                              cimg_instance,
+                              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_instance
+                              "load_png() : Failed to initialize 'end_info' structure for file '%s'.",
+                              cimg_instance,
+                              nfilename?nfilename:"(FILE*)");
+      }
+
+      // Error handling callback for png file reading
+      if (setjmp(png_jmpbuf(png_ptr))) {
+        if (!file) cimg::fclose((std::FILE*)nfile);
+        png_destroy_read_struct(&png_ptr, &end_info, (png_infopp)0);
+        throw CImgIOException(_cimg_instance
+                              "load_png() : Encountered unknown fatal error in libpng for file '%s'.",
+                              cimg_instance,
+                              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;
+      bool is_gray = false;
+      png_get_IHDR(png_ptr,info_ptr,&W,&H,&bit_depth,&color_type,&interlace_type,(int*)0,(int*)0);
+
+      // Transforms to unify image data
+      if (color_type==PNG_COLOR_TYPE_PALETTE) {
+        png_set_palette_to_rgb(png_ptr);
+        color_type = PNG_COLOR_TYPE_RGB;
+        bit_depth = 8;
+      }
+      if (color_type==PNG_COLOR_TYPE_GRAY && bit_depth<8) {
+        png_set_expand_gray_1_2_4_to_8(png_ptr);
+        is_gray = true;
+        bit_depth = 8;
+      }
+      if (png_get_valid(png_ptr,info_ptr,PNG_INFO_tRNS)) {
+        png_set_tRNS_to_alpha(png_ptr);
+        color_type |= PNG_COLOR_MASK_ALPHA;
+      }
+      if (color_type==PNG_COLOR_TYPE_GRAY || color_type==PNG_COLOR_TYPE_GRAY_ALPHA) {
+        png_set_gray_to_rgb(png_ptr);
+        color_type |= PNG_COLOR_MASK_COLOR;
+        is_gray = true;
+      }
+      if (color_type==PNG_COLOR_TYPE_RGB)
+        png_set_filler(png_ptr,0xffffU,PNG_FILLER_AFTER);
+
+      png_read_update_info(png_ptr,info_ptr);
+      if (bit_depth!=8 && bit_depth!=16) {
+        if (!file) cimg::fclose(nfile);
+        png_destroy_read_struct(&png_ptr,&end_info,(png_infopp)0);
+        throw CImgIOException(_cimg_instance
+                              "load_png() : Invalid bit depth %u in file '%s'.",
+                              cimg_instance,
+                              bit_depth,nfilename?nfilename:"(FILE*)");
+      }
+      const int byte_depth = bit_depth>>3;
+
+      // Allocate Memory for Image Read
+      png_bytep *const 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 (color_type!=PNG_COLOR_TYPE_RGB && 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_instance
+                              "load_png() : Invalid color coding type %u in file '%s'.",
+                              cimg_instance,
+                              color_type,nfilename?nfilename:"(FILE*)");
+      }
+      const bool is_alpha = (color_type==PNG_COLOR_TYPE_RGBA);
+      assign(W,H,1,(is_gray?1:3) + (is_alpha?1:0));
+      T
+        *ptr_r = data(0,0,0,0),
+        *ptr_g = is_gray?0:data(0,0,0,1),
+        *ptr_b = is_gray?0:data(0,0,0,2),
+        *ptr_a = !is_alpha?0:data(0,0,0,is_gray?1:3);
+      switch (bit_depth) {
+      case 8 : {
+        cimg_forY(*this,y) {
+          const unsigned char *ptrs = (unsigned char*)imgData[y];
+          cimg_forX(*this,x) {
+            *(ptr_r++) = (T)*(ptrs++);
+            if (ptr_g) *(ptr_g++) = (T)*(ptrs++); else ++ptrs;
+            if (ptr_b) *(ptr_b++) = (T)*(ptrs++); else ++ptrs;
+            if (ptr_a) *(ptr_a++) = (T)*(ptrs++); else ++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) {
+            *(ptr_r++) = (T)*(ptrs++);
+            if (ptr_g) *(ptr_g++) = (T)*(ptrs++); else ++ptrs;
+            if (ptr_b) *(ptr_b++) = (T)*(ptrs++); else ++ptrs;
+            if (ptr_a) *(ptr_a++) = (T)*(ptrs++); else ++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(std::FILE *const file) {
+      return _load_pnm(file,0);
+    }
+
+    static CImg<T> get_load_pnm(std::FILE *const file) {
+      return CImg<T>().load_pnm(file);
+    }
+
+    CImg<T>& _load_pnm(std::FILE *const file, const char *const filename) {
+      if (!file && !filename)
+        throw CImgArgumentException(_cimg_instance
+                                    "load_pnm() : Specified filename is (null).",
+                                    cimg_instance);
+
+      std::FILE *const nfile = file?file:cimg::fopen(filename,"rb");
+      unsigned int ppm_type, W, H, D = 1, colormax = 255;
+      char item[1024] = { 0 };
+      int err, rval, gval, bval;
+      const long cimg_iobuffer = 12*1024*1024;
+      while ((err=std::fscanf(nfile,"%1023[^\n]",item))!=EOF && (*item=='#' || !err)) std::fgetc(nfile);
+      if (std::sscanf(item," P%u",&ppm_type)!=1) {
+        if (!file) cimg::fclose(nfile);
+        throw CImgIOException(_cimg_instance
+                              "load_pnm() : PNM header not found in file '%s'.",
+                              cimg_instance,
+                              filename?filename:"(FILE*)");
+      }
+      while ((err=std::fscanf(nfile," %1023[^\n]",item))!=EOF && (*item=='#' || !err)) std::fgetc(nfile);
+      if ((err=std::sscanf(item," %u %u %u %u",&W,&H,&D,&colormax))<2) {
+        if (!file) cimg::fclose(nfile);
+        throw CImgIOException(_cimg_instance
+                              "load_pnm() : WIDTH and HEIGHT fields undefined in file '%s'.",
+                              cimg_instance,
+                              filename?filename:"(FILE*)");
+      }
+      if (ppm_type!=1 && ppm_type!=4) {
+        if (err==2 || (err==3 && (ppm_type==5 || ppm_type==7 || ppm_type==8 || ppm_type==9))) {
+          while ((err=std::fscanf(nfile," %1023[^\n]",item))!=EOF && (*item=='#' || !err)) std::fgetc(nfile);
+          if (std::sscanf(item,"%u",&colormax)!=1)
+            cimg::warn(_cimg_instance
+                       "load_pnm() : COLORMAX field is undefined in file '%s'.",
+                       cimg_instance,
+                       filename?filename:"(FILE*)");
+        } else { colormax = D; D = 1; }
+      }
+      std::fgetc(nfile);
+
+      switch (ppm_type) {
+      case 1 : { // 2d b&w ascii.
+        assign(W,H,1,1);
+        T* ptrd = _data;
+        cimg_foroff(*this,off) { if (std::fscanf(nfile,"%d",&rval)>0) *(ptrd++) = (T)(rval?0:255); else break; }
+      } break;
+      case 2 : { // 2d grey ascii.
+        assign(W,H,1,1);
+        T* ptrd = _data;
+        cimg_foroff(*this,off) { if (std::fscanf(nfile,"%d",&rval)>0) *(ptrd++) = (T)rval; else break; }
+      } break;
+      case 3 : { // 2d color ascii.
+        assign(W,H,1,3);
+        T *ptrd = data(0,0,0,0), *ptr_g = data(0,0,0,1), *ptr_b = data(0,0,0,2);
+        cimg_forXY(*this,x,y) {
+          if (std::fscanf(nfile,"%d %d %d",&rval,&gval,&bval)==3) { *(ptrd++) = (T)rval; *(ptr_g++) = (T)gval; *(ptr_b++) = (T)bval; }
+          else break;
+        }
+      } break;
+      case 4 : { // 2d b&w binary (support 3D PINK extension).
+        CImg<ucharT> raw;
+        assign(W,H,D,1);
+        T *ptrd = data(0,0,0,0);
+        unsigned int w = 0, h = 0, d = 0;
+        for (long to_read = (long)((W/8 + (W%8?1:0))*H*D); to_read>0; ) {
+          raw.assign(cimg::min(to_read,cimg_iobuffer));
+          cimg::fread(raw._data,raw._width,nfile);
+          to_read-=raw._width;
+          const unsigned char *ptrs = raw._data;
+          unsigned char mask = 0, val = 0;
+          for (unsigned long off = (unsigned long)raw._width; off || mask; mask>>=1) {
+            if (!mask) { if (off--) val = *(ptrs++); mask = 128; }
+            *(ptrd++) = (T)((val&mask)?0:255);
+            if (++w==W) { w = 0; mask = 0; if (++h==H) { h = 0; if (++d==D) break; }}
+          }
+        }
+      } break;
+      case 5 : case 7 : { // 2d/3d grey binary (support 3D PINK extension).
+        if (colormax<256) { // 8 bits.
+          CImg<ucharT> raw;
+          assign(W,H,D,1);
+          T *ptrd = data(0,0,0,0);
+          for (long to_read = (long)size(); to_read>0; ) {
+            raw.assign(cimg::min(to_read,cimg_iobuffer));
+            cimg::fread(raw._data,raw._width,nfile);
+            to_read-=raw._width;
+            const unsigned char *ptrs = raw._data;
+            for (unsigned long off = (unsigned long)raw._width; off; --off) *(ptrd++) = (T)*(ptrs++);
+          }
+        } else { // 16 bits.
+          CImg<ushortT> raw;
+          assign(W,H,D,1);
+          T *ptrd = data(0,0,0,0);
+          for (long to_read = (long)size(); to_read>0; ) {
+            raw.assign(cimg::min(to_read,cimg_iobuffer/2));
+            cimg::fread(raw._data,raw._width,nfile);
+            if (!cimg::endianness()) cimg::invert_endianness(raw._data,raw._width);
+            to_read-=raw._width;
+            const unsigned short *ptrs = raw._data;
+            for (unsigned long off = (unsigned long)raw._width; off; --off) *(ptrd++) = (T)*(ptrs++);
+          }
+        }
+      } break;
+      case 6 : { // 2d color binary.
+        if (colormax<256) { // 8 bits.
+          CImg<ucharT> raw;
+          assign(W,H,1,3);
+          T
+            *ptr_r = data(0,0,0,0),
+            *ptr_g = data(0,0,0,1),
+            *ptr_b = data(0,0,0,2);
+          for (long to_read = (long)size(); to_read>0; ) {
+            raw.assign(cimg::min(to_read,cimg_iobuffer));
+            cimg::fread(raw._data,raw._width,nfile);
+            to_read-=raw._width;
+            const unsigned char *ptrs = raw._data;
+            for (unsigned long off = (unsigned long)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 = data(0,0,0,0),
+            *ptr_g = data(0,0,0,1),
+            *ptr_b = data(0,0,0,2);
+          for (long to_read = (int)size(); to_read>0; ) {
+            raw.assign(cimg::min(to_read,cimg_iobuffer/2));
+            cimg::fread(raw._data,raw._width,nfile);
+            if (!cimg::endianness()) cimg::invert_endianness(raw._data,raw._width);
+            to_read-=raw._width;
+            const unsigned short *ptrs = raw._data;
+            for (unsigned long off = (unsigned long)raw._width/3; off; --off) {
+              *(ptr_r++) = (T)*(ptrs++);
+              *(ptr_g++) = (T)*(ptrs++);
+              *(ptr_b++) = (T)*(ptrs++);
+            }
+          }
+        }
+      } break;
+      case 8 : { // 2d/3d grey binary with int32 integers (PINK extension).
+        CImg<intT> raw;
+        assign(W,H,D,1);
+        T *ptrd = data(0,0,0,0);
+        for (long to_read = (long)size(); to_read>0; ) {
+          raw.assign(cimg::min(to_read,cimg_iobuffer));
+          cimg::fread(raw._data,raw._width,nfile);
+          to_read-=raw._width;
+          const int *ptrs = raw._data;
+          for (unsigned long off = (unsigned long)raw._width; off; --off) *(ptrd++) = (T)*(ptrs++);
+        }
+      } break;
+      case 9 : { // 2d/3d grey binary with float values (PINK extension).
+        CImg<floatT> raw;
+        assign(W,H,D,1);
+        T *ptrd = data(0,0,0,0);
+        for (long to_read = (long)size(); to_read>0; ) {
+          raw.assign(cimg::min(to_read,cimg_iobuffer));
+          cimg::fread(raw._data,raw._width,nfile);
+          to_read-=raw._width;
+          const float *ptrs = raw._data;
+          for (unsigned long off = (unsigned long)raw._width; off; --off) *(ptrd++) = (T)*(ptrs++);
+        }
+      } break;
+      default :
+        assign();
+        if (!file) cimg::fclose(nfile);
+        throw CImgIOException(_cimg_instance
+                              "load_pnm() : PNM type 'P%d' found, but type is not supported.",
+                              cimg_instance,
+                              filename?filename:"(FILE*)",ppm_type);
+      }
+      if (!file) cimg::fclose(nfile);
+      return *this;
+    }
+
+    //! Load an image from a PFM file.
+    CImg<T>& load_pfm(const char *const filename) {
+      return _load_pfm(0,filename);
+    }
+
+    static CImg<T> get_load_pfm(const char *const filename) {
+      return CImg<T>().load_pfm(filename);
+    }
+
+    //! Load an image from a PFM file.
+    CImg<T>& load_pfm(std::FILE *const file) {
+      return _load_pfm(file,0);
+    }
+
+    static CImg<T> get_load_pfm(std::FILE *const file) {
+      return CImg<T>().load_pfm(file);
+    }
+
+    CImg<T>& _load_pfm(std::FILE *const file, const char *const filename) {
+      if (!file && !filename)
+        throw CImgArgumentException(_cimg_instance
+                                    "load_pfm() : Specified filename is (null).",
+                                    cimg_instance);
+
+      std::FILE *const nfile = file?file:cimg::fopen(filename,"rb");
+      char pfm_type, item[1024] = { 0 };
+      int W = 0, H = 0, err = 0;
+      double scale = 0;
+      while ((err=std::fscanf(nfile,"%1023[^\n]",item))!=EOF && (*item=='#' || !err)) std::fgetc(nfile);
+      if (std::sscanf(item," P%c",&pfm_type)!=1) {
+        if (!file) cimg::fclose(nfile);
+        throw CImgIOException(_cimg_instance
+                              "load_pfm() : PFM header not found in file '%s'.",
+                              cimg_instance,
+                              filename?filename:"(FILE*)");
+      }
+      while ((err=std::fscanf(nfile," %1023[^\n]",item))!=EOF && (*item=='#' || !err)) std::fgetc(nfile);
+      if ((err=std::sscanf(item," %d %d",&W,&H))<2) {
+        if (!file) cimg::fclose(nfile);
+        throw CImgIOException(_cimg_instance
+                              "load_pfm() : WIDTH and HEIGHT fields are undefined in file '%s'.",
+                              cimg_instance,
+                              filename?filename:"(FILE*)");
+      }
+      if (err==2) {
+        while ((err=std::fscanf(nfile," %1023[^\n]",item))!=EOF && (*item=='#' || !err)) std::fgetc(nfile);
+        if (std::sscanf(item,"%lf",&scale)!=1)
+          cimg::warn(_cimg_instance
+                     "load_pfm() : SCALE field is undefined in file '%s'.",
+                     cimg_instance,
+                     filename?filename:"(FILE*)");
+      }
+      std::fgetc(nfile);
+      const bool is_color = (pfm_type=='F'), is_inverted = (scale>0)!=cimg::endianness();
+      if (is_color) {
+        assign(W,H,1,3,0);
+        CImg<floatT> buf(3*W);
+        T *ptr_r = data(0,0,0,0), *ptr_g = data(0,0,0,1), *ptr_b = data(0,0,0,2);
+        cimg_forY(*this,y) {
+          cimg::fread(buf._data,3*W,nfile);
+          if (is_inverted) cimg::invert_endianness(buf._data,3*W);
+          const float *ptrs = buf._data;
+          cimg_forX(*this,x) {
+            *(ptr_r++) = (T)*(ptrs++);
+            *(ptr_g++) = (T)*(ptrs++);
+            *(ptr_b++) = (T)*(ptrs++);
+          }
+        }
+      } else {
+        assign(W,H,1,1,0);
+        CImg<floatT> buf(W);
+        T *ptrd = data(0,0,0,0);
+        cimg_forY(*this,y) {
+          cimg::fread(buf._data,W,nfile);
+          if (is_inverted) cimg::invert_endianness(buf._data,W);
+          const float *ptrs = buf._data;
+          cimg_forX(*this,x) *(ptrd++) = (T)*(ptrs++);
+        }
+      }
+      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(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(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(std::FILE *const file, const char *const filename, const unsigned int dimw, const unsigned int dimh) {
+      if (!file && !filename)
+        throw CImgArgumentException(_cimg_instance
+                                    "load_rgb() : Specified filename is (null).",
+                                    cimg_instance);
+
+      if (!dimw || !dimh) return assign();
+      const long cimg_iobuffer = 12*1024*1024;
+      std::FILE *const nfile = file?file:cimg::fopen(filename,"rb");
+      CImg<ucharT> raw;
+      assign(dimw,dimh,1,3);
+      T
+        *ptr_r = data(0,0,0,0),
+        *ptr_g = data(0,0,0,1),
+        *ptr_b = data(0,0,0,2);
+      for (long to_read = (long)size(); to_read>0; ) {
+        raw.assign(cimg::min(to_read,cimg_iobuffer));
+        cimg::fread(raw._data,raw._width,nfile);
+        to_read-=raw._width;
+        const unsigned char *ptrs = raw._data;
+        for (unsigned long off = raw._width/3UL; 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(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(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(std::FILE *const file, const char *const filename, const unsigned int dimw, const unsigned int dimh) {
+      if (!file && !filename)
+        throw CImgArgumentException(_cimg_instance
+                                    "load_rgba() : Specified filename is (null).",
+                                    cimg_instance);
+
+      if (!dimw || !dimh) return assign();
+      const long cimg_iobuffer = 12*1024*1024;
+      std::FILE *const nfile = file?file:cimg::fopen(filename,"rb");
+      CImg<ucharT> raw;
+      assign(dimw,dimh,1,4);
+      T
+        *ptr_r = data(0,0,0,0),
+        *ptr_g = data(0,0,0,1),
+        *ptr_b = data(0,0,0,2),
+        *ptr_a = data(0,0,0,3);
+      for (long to_read = (long)size(); to_read>0; ) {
+        raw.assign(cimg::min(to_read,cimg_iobuffer));
+        cimg::fread(raw._data,raw._width,nfile);
+        to_read-=raw._width;
+        const unsigned char *ptrs = raw._data;
+        for (unsigned long off = raw._width/4UL; 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.
+    /**
+       - libtiff support is enabled by defining the precompilation
+        directive cimg_use_tif.
+
+       - When libtiff is enabled, 2D and 3D (multipage) several
+        channel per pixel are supported for
+        char,uchar,short,ushort,float and double pixel type.
+
+       - If cimg_use_tif is not defined at compilation time the
+        function uses CImg<T>&load_other(const char*).
+
+       \see CImg<T>& load_other(const char*)
+       \see CImg<T>& save_tiff(const char*, const unsigned int)
+     **/
+    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_instance
+                                    "load_tiff() : Specified filename is (null).",
+                                    cimg_instance);
+
+      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_instance
+                                    "load_tiff() : Unable to read sub-images from file '%s' unless libtiff is enabled.",
+                                    cimg_instance,
+                                    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_instance
+                     "load_tiff() : File '%s' contains %u image(s) while specified frame range is [%u,%u] (step %u).",
+                     cimg_instance,
+                     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._spectrum);
+          if (frame._width>_width || frame._height>_height || frame._spectrum>_spectrum)
+            resize(cimg::max(frame._width,_width),cimg::max(frame._height,_height),-100,cimg::max(frame._spectrum,_spectrum),0);
+          draw_image(0,0,(l-nfirst_frame)/nstep_frame,frame);
+        }
+        TIFFClose(tif);
+      } else throw CImgIOException(_cimg_instance
+                                   "load_tiff() : Failed to open file '%s'.",
+                                   cimg_instance,
+                                   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
+    template<typename t>
+    void _load_tiff_tiled_contig(TIFF *const tif, const uint16 samplesperpixel, const uint32 nx, const uint32 ny, const uint32 tw, const uint32 th) {
+      t *const buf = (t*)_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 CImgIOException(_cimg_instance
+                                    "load_tiff() : Invalid tile in file '%s'.",
+                                    cimg_instance,
+                                    TIFFFileName(tif));
+            }
+            const t *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)(ptr[(rr-row)*th*samplesperpixel + (cc-col)*samplesperpixel + vv]);
+          }
+        _TIFFfree(buf);
+      }
+    }
+
+    template<typename t>
+    void _load_tiff_tiled_separate(TIFF *const tif, const uint16 samplesperpixel, const uint32 nx, const uint32 ny, const uint32 tw, const uint32 th) {
+      t *const buf = (t*)_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 CImgIOException(_cimg_instance
+                                      "load_tiff() : Invalid tile in file '%s'.",
+                                      cimg_instance,
+                                      TIFFFileName(tif));
+              }
+              const t *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)*(ptr++);
+            }
+        _TIFFfree(buf);
+      }
+    }
+
+    template<typename t>
+    void _load_tiff_contig(TIFF *const tif, const uint16 samplesperpixel, const uint32 nx, const uint32 ny) {
+      t *const buf = (t*)_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 CImgIOException(_cimg_instance
+                                  "load_tiff() : Invalid strip in file '%s'.",
+                                  cimg_instance,
+                                  TIFFFileName(tif));
+          }
+          const t *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)*(ptr++);
+        }
+        _TIFFfree(buf);
+      }
+    }
+
+    template<typename t>
+    void _load_tiff_separate(TIFF *const tif, const uint16 samplesperpixel, const uint32 nx, const uint32 ny) {
+      t *buf = (t*)_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 CImgIOException(_cimg_instance
+                                    "load_tiff() : Invalid strip in file '%s'.",
+                                    cimg_instance,
+                                    TIFFFileName(tif));
+            }
+            const t *ptr = buf;
+            for (unsigned int rr = 0;rr<nrow; ++rr)
+              for (unsigned int cc = 0; cc<nx; ++cc)
+                (*this)(cc,row+rr,vv) = (T)*(ptr++);
+          }
+        _TIFFfree(buf);
+      }
+    }
+
+    CImg<T>& _load_tiff(TIFF *const tif, const unsigned int directory) {
+      if (!TIFFSetDirectory(tif,directory)) return assign();
+      uint16 samplesperpixel, bitspersample;
+      uint16 sampleformat = SAMPLEFORMAT_UINT;
+      uint32 nx,ny;
+      const char *const filename = TIFFFileName(tif);
+      TIFFGetField(tif,TIFFTAG_IMAGEWIDTH,&nx);
+      TIFFGetField(tif,TIFFTAG_IMAGELENGTH,&ny);
+      TIFFGetFieldDefaulted(tif,TIFFTAG_SAMPLESPERPIXEL,&samplesperpixel);
+      TIFFGetFieldDefaulted(tif, TIFFTAG_SAMPLEFORMAT, &sampleformat);
+      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 : {
+              if (sampleformat==SAMPLEFORMAT_UINT) _load_tiff_tiled_contig<unsigned char>(tif,samplesperpixel,nx,ny,tw,th);
+              else _load_tiff_tiled_contig<signed char>(tif,samplesperpixel,nx,ny,tw,th);
+            } break;
+            case 16 :
+              if (sampleformat==SAMPLEFORMAT_UINT) _load_tiff_tiled_contig<unsigned short>(tif,samplesperpixel,nx,ny,tw,th);
+              else _load_tiff_tiled_contig<short>(tif,samplesperpixel,nx,ny,tw,th);
+              break;
+            case 32 :
+              if (sampleformat==SAMPLEFORMAT_UINT) _load_tiff_tiled_contig<unsigned int>(tif,samplesperpixel,nx,ny,tw,th);
+              else if (sampleformat==SAMPLEFORMAT_INT) _load_tiff_tiled_contig<int>(tif,samplesperpixel,nx,ny,tw,th);
+              else _load_tiff_tiled_contig<float>(tif,samplesperpixel,nx,ny,tw,th);
+              break;
+            } else switch (bitspersample) {
+            case 8 :
+              if (sampleformat==SAMPLEFORMAT_UINT) _load_tiff_tiled_separate<unsigned char>(tif,samplesperpixel,nx,ny,tw,th);
+              else _load_tiff_tiled_separate<signed char>(tif,samplesperpixel,nx,ny,tw,th);
+              break;
+            case 16 :
+              if (sampleformat==SAMPLEFORMAT_UINT) _load_tiff_tiled_separate<unsigned short>(tif,samplesperpixel,nx,ny,tw,th);
+              else _load_tiff_tiled_separate<short>(tif,samplesperpixel,nx,ny,tw,th);
+              break;
+            case 32 :
+              if (sampleformat==SAMPLEFORMAT_UINT) _load_tiff_tiled_separate<unsigned int>(tif,samplesperpixel,nx,ny,tw,th);
+              else if (sampleformat==SAMPLEFORMAT_INT) _load_tiff_tiled_separate<int>(tif,samplesperpixel,nx,ny,tw,th);
+              else _load_tiff_tiled_separate<float>(tif,samplesperpixel,nx,ny,tw,th);
+              break;
+            }
+        } else {
+          if (config==PLANARCONFIG_CONTIG) switch (bitspersample) {
+            case 8 :
+              if (sampleformat==SAMPLEFORMAT_UINT) _load_tiff_contig<unsigned char>(tif,samplesperpixel,nx,ny);
+              else _load_tiff_contig<signed char>(tif,samplesperpixel,nx,ny);
+              break;
+            case 16 :
+              if (sampleformat==SAMPLEFORMAT_UINT) _load_tiff_contig<unsigned short>(tif,samplesperpixel,nx,ny);
+              else _load_tiff_contig<short>(tif,samplesperpixel,nx,ny);
+              break;
+            case 32 :
+              if (sampleformat==SAMPLEFORMAT_UINT) _load_tiff_contig<unsigned int>(tif,samplesperpixel,nx,ny);
+              else if (sampleformat==SAMPLEFORMAT_INT) _load_tiff_contig<int>(tif,samplesperpixel,nx,ny);
+              else _load_tiff_contig<float>(tif,samplesperpixel,nx,ny);
+              break;
+            } else switch (bitspersample){
+            case 8 :
+              if (sampleformat==SAMPLEFORMAT_UINT) _load_tiff_separate<unsigned char>(tif,samplesperpixel,nx,ny);
+              else _load_tiff_separate<signed char>(tif,samplesperpixel,nx,ny);
+              break;
+            case 16 :
+              if (sampleformat==SAMPLEFORMAT_UINT) _load_tiff_separate<unsigned short>(tif,samplesperpixel,nx,ny);
+              else _load_tiff_separate<short>(tif,samplesperpixel,nx,ny);
+              break;
+            case 32 :
+              if (sampleformat==SAMPLEFORMAT_UINT) _load_tiff_separate<unsigned int>(tif,samplesperpixel,nx,ny);
+              else if (sampleformat==SAMPLEFORMAT_INT) _load_tiff_separate<int>(tif,samplesperpixel,nx,ny);
+              else _load_tiff_separate<float>(tif,samplesperpixel,nx,ny);
+              break;
+            }
+        }
+      } else {
+        uint32 *const raster = (uint32*)_TIFFmalloc(nx*ny*sizeof(uint32));
+        if (!raster) {
+          _TIFFfree(raster); TIFFClose(tif);
+          throw CImgIOException(_cimg_instance
+                                "load_tiff() : Failed to allocate memory (%s) for file '%s'.",
+                                cimg_instance,
+                                cimg::strbuffersize(nx*ny*sizeof(uint32)),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 a MINC2 file.
+    // (Original code by Haz-Edine Assemlal).
+    CImg<T>& load_minc2(const char *const filename) {
+      if (!filename)
+        throw CImgArgumentException(_cimg_instance
+                                    "load_minc2() : Specified filename is (null).",
+                                    cimg_instance);
+#ifndef cimg_use_minc2
+      return load_other(filename);
+#else
+
+      // Open the MINC2 volume in read-only access.
+      mihandle_t hvol;
+      int result = miopen_volume(filename,MI2_OPEN_RDWR,&hvol);
+      if (result!=MI_NOERROR)
+        throw CImgIOException(_cimg_instance
+                              "load_minc2() : Invalid MINC2 format for file '%s'.",
+                              cimg_instance,filename);
+
+      mitype_t volume_data_type;
+      result = miget_data_type(hvol,&volume_data_type);
+
+      int slice_scaling_flag;
+      result = miget_slice_scaling_flag(hvol,&slice_scaling_flag);
+
+      double valid_max, valid_min;
+      result = miget_volume_valid_range(hvol,&valid_max,&valid_min);
+
+      miclass_t volume_data_class;
+      result = miget_data_class(hvol,&volume_data_class);
+
+      midimhandle_t *const hdims = (midimhandle_t*)std::malloc(4*sizeof(midimhandle_t));
+      result = miget_volume_dimensions(hvol,MI_DIMCLASS_ANY,MI_DIMATTR_ALL,MI_DIMORDER_FILE,4,hdims);
+
+      CImg<uintT> minc_dims(4,1,1,1,0);
+      cimg_forX(minc_dims,d) {
+        miboolean_t irregular;
+        result = miget_dimension_sampling_flag(hdims[d],&irregular);
+        if (irregular) {
+          char* name;
+          result = miget_dimension_name(hdims[d],&name);
+          const CImg<charT> _name = CImg<charT>::string(name);
+          mifree_name(name);
+          throw CImgIOException(_cimg_instance
+                                "load_minc2() : Unsupported dimension (%s) detected in file '%s'.",
+                                cimg_instance,name,filename);
+        }
+      }
+
+      result = miget_dimension_sizes(hdims,4,minc_dims.data());
+      miflipping_t file_order;
+      miflipping_t sign;
+      result = miget_dimension_apparent_voxel_order(*hdims,&file_order,&sign);
+      if (file_order)
+        cimg::warn(_cimg_instance
+                   "load_minc2() : Unsupported voxel order (%d) detected in file '%s'.",
+                   cimg_instance,file_order,filename);
+
+      CImg<intT> permutations, permutations_inv;
+      const CImg<uintT>
+        minc_dims_sort = minc_dims.get_sort(permutations,true),
+        minc_dims_sort_inv = minc_dims.get_sort(permutations_inv,false);
+
+      int nb_useful_dims = 0;
+      if (minc_dims(0)!=0) ++nb_useful_dims;
+      if (minc_dims(1)!=0) ++nb_useful_dims;
+      if (minc_dims(2)!=0) ++nb_useful_dims;
+      if (minc_dims(3)!=0) ++nb_useful_dims;
+
+      // BUG in MINC2 ? count as to be equal to 4, not to nb_useful_dims.
+      CImg<ulongT> count(4,1,1,1,1);
+      unsigned int c = 0;
+      cimg_foroff(minc_dims,d) if (minc_dims(d)) count(c++) = minc_dims(d);
+      assign(!minc_dims(0)?1:minc_dims(0),
+             !minc_dims(1)?1:minc_dims(1),
+             !minc_dims(2)?1:minc_dims(2),
+             !minc_dims(3)?1:minc_dims(3));
+      if (nb_useful_dims==2) permute_axes("yxzc");
+      else if (nb_useful_dims==3) permute_axes("zyxc");
+      else if (nb_useful_dims==4) permute_axes("czyx");
+
+      // Read the entire file in one operation.
+      CImg<ulongT> start(4,1,1,1,0);
+      if (slice_scaling_flag) result = miget_real_value_hyperslab(hvol,MI_TYPE_FLOAT,start.data(),count.data(),data());
+      else result = miget_voxel_value_hyperslab(hvol,MI_TYPE_FLOAT,start.data(),count.data(),data());
+
+      // Close the MINC2 volume.
+      result = mifree_dimension_handle(*hdims);
+      miclose_volume(hvol);
+      return mirror('y');
+#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(std::FILE *const file, float *const voxsize=0) {
+      return _load_analyze(file,0,voxsize);
+    }
+
+    static CImg<T> get_load_analyze(std::FILE *const file, float *const voxsize=0) {
+      return CImg<T>().load_analyze(file,voxsize);
+    }
+
+    CImg<T>& _load_analyze(std::FILE *const file, const char *const filename, float *const voxsize=0) {
+      if (!file && !filename)
+        throw CImgArgumentException(_cimg_instance
+                                    "load_analyze() : Specified filename is (null).",
+                                    cimg_instance);
+
+      std::FILE *nfile_header = 0, *nfile = 0;
+      if (!file) {
+        char body[1024] = { 0 };
+        const char *const ext = cimg::split_filename(filename,body);
+        if (!cimg::strcasecmp(ext,"hdr")) { // File is an Analyze header file.
+          nfile_header = cimg::fopen(filename,"rb");
+          std::sprintf(body + std::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");
+          std::sprintf(body + std::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_instance
+                              "load_analyze() : Invalid Analyze7.5 or NIFTI header in file '%s'.",
+                              cimg_instance,
+                              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_instance
+                              "load_analyze() : Invalid zero-sized header in file '%s'.",
+                              cimg_instance,
+                              filename?filename:"(FILE*)");
+
+      if (header_size>=4096) { endian = true; cimg::invert_endianness(header_size); }
+      unsigned char *const 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_instance
+                   "load_analyze() : File '%s' defines an image with zero dimensions.",
+                   cimg_instance,
+                   filename?filename:"(FILE*)");
+
+      if (dim[0]>4)
+        cimg::warn(_cimg_instance
+                   "load_analyze() : File '%s' defines an image with %u dimensions, reading only the 4 first.",
+                   cimg_instance,
+                   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 *const 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 *const 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 *const 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 *const 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 *const 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_instance
+                              "load_analyze() : Unable to load datatype %d in file '%s'",
+                              cimg_instance,
+                              datatype,filename?filename:"(FILE*)");
+      }
+      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 float align=0) {
+      CImgList<T> list;
+      list.load_cimg(filename);
+      if (list._width==1) return list[0].move_to(*this);
+      return assign(list.get_append(axis,align));
+    }
+
+    static CImg<T> get_load_cimg(const char *const filename, const char axis='z', const float align=0) {
+      return CImg<T>().load_cimg(filename,axis,align);
+    }
+
+    //! Load an image (list) from a .cimg file.
+    CImg<T>& load_cimg(std::FILE *const file, const char axis='z', const float align=0) {
+      CImgList<T> list;
+      list.load_cimg(file);
+      if (list._width==1) return list[0].move_to(*this);
+      return assign(list.get_append(axis,align));
+    }
+
+    static CImg<T> get_load_cimg(std::FILE *const file, const char axis='z', const float align=0) {
+      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 c0,
+                       const unsigned int x1, const unsigned int y1, const unsigned int z1, const unsigned int c1,
+                       const char axis='z', const float align=0) {
+      CImgList<T> list;
+      list.load_cimg(filename,n0,n1,x0,y0,z0,c0,x1,y1,z1,c1);
+      if (list._width==1) return list[0].move_to(*this);
+      return assign(list.get_append(axis,align));
+    }
+
+    static CImg<T> get_load_cimg(const char *const filename,
+                                 const unsigned int n0, const unsigned int n1,
+                                 const unsigned int x0, const unsigned int y0, const unsigned int z0, const unsigned int c0,
+                                 const unsigned int x1, const unsigned int y1, const unsigned int z1, const unsigned int c1,
+                                 const char axis='z', const float align=0) {
+      return CImg<T>().load_cimg(filename,n0,n1,x0,y0,z0,c0,x1,y1,z1,c1,axis,align);
+    }
+
+    //! Load a sub-image (list) from a non-compressed .cimg file.
+    CImg<T>& load_cimg(std::FILE *const file,
+                       const unsigned int n0, const unsigned int n1,
+                       const unsigned int x0, const unsigned int y0, const unsigned int z0, const unsigned int c0,
+                       const unsigned int x1, const unsigned int y1, const unsigned int z1, const unsigned int c1,
+                       const char axis='z', const float align=0) {
+      CImgList<T> list;
+      list.load_cimg(file,n0,n1,x0,y0,z0,c0,x1,y1,z1,c1);
+      if (list._width==1) return list[0].move_to(*this);
+      return assign(list.get_append(axis,align));
+    }
+
+    static CImg<T> get_load_cimg(std::FILE *const file,
+                                 const unsigned int n0, const unsigned int n1,
+                                 const unsigned int x0, const unsigned int y0, const unsigned int z0, const unsigned int c0,
+                                 const unsigned int x1, const unsigned int y1, const unsigned int z1, const unsigned int c1,
+                                 const char axis='z', const float align=0) {
+      return CImg<T>().load_cimg(file,n0,n1,x0,y0,z0,c0,x1,y1,z1,c1,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(std::FILE *const file, float *const voxsize=0) {
+      return _load_inr(file,0,voxsize);
+    }
+
+    static CImg<T> get_load_inr(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(std::FILE *file, int out[8], float *const voxsize) {
+      char item[1024] = { 0 }, tmp1[64] = { 0 }, tmp2[64] = { 0 };
+      out[0] = 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() : INRIMAGE-4 header not found.",
+                              pixel_type());
+
+      while (std::fscanf(file," %63[^\n]%*c",item)!=EOF && std::strncmp(item,"##}",3)) {
+        std::sscanf(item," XDIM%*[^0-9]%d",out);
+        std::sscanf(item," YDIM%*[^0-9]%d",out+1);
+        std::sscanf(item," ZDIM%*[^0-9]%d",out+2);
+        std::sscanf(item," VDIM%*[^0-9]%d",out+3);
+        std::sscanf(item," PIXSIZE%*[^0-9]%d",out+6);
+        if (voxsize) {
+          std::sscanf(item," VX%*[^0-9.+-]%f",voxsize);
+          std::sscanf(item," VY%*[^0-9.+-]%f",voxsize+1);
+          std::sscanf(item," VZ%*[^0-9.+-]%f",voxsize+2);
+        }
+        if (std::sscanf(item," CPU%*[ =]%s",tmp1)) out[7]=cimg::strncasecmp(tmp1,"sun",3)?0:1;
+        switch (std::sscanf(item," TYPE%*[ =]%s %s",tmp1,tmp2)) {
+        case 0 : break;
+        case 2 : out[5] = cimg::strncasecmp(tmp1,"unsigned",8)?1:0; std::strncpy(tmp1,tmp2,sizeof(tmp1)-1);
+        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<%s>::load_inr() : Invalid pixel type '%s' defined in header.",
+                                pixel_type(),
+                                tmp2);
+        }
+      }
+      if(out[0]<0 || out[1]<0 || out[2]<0 || out[3]<0)
+        throw CImgIOException("CImg<%s>::load_inr() : Invalid dimensions (%d,%d,%d,%d) defined in header.",
+                              pixel_type(),
+                              out[0],out[1],out[2],out[3]);
+      if(out[4]<0 || out[5]<0)
+        throw CImgIOException("CImg<%s>::load_inr() : Incomplete pixel type defined in header.",
+                              pixel_type());
+      if(out[6]<0)
+        throw CImgIOException("CImg<%s>::load_inr() : Incomplete PIXSIZE field defined in header.",
+                              pixel_type());
+      if(out[7]<0)
+        throw CImgIOException("CImg<%s>::load_inr() : Big/Little Endian coding type undefined in header.",
+                              pixel_type());
+    }
+
+    CImg<T>& _load_inr(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, *const 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_forC(*this,c) (*this)(x,y,z,c) = (T)*(xval++); \
+          } \
+        delete[] val; \
+        loaded = true; \
+      }
+
+      if (!file && !filename)
+        throw CImgArgumentException(_cimg_instance
+                                    "load_inr() : Specified filename is (null).",
+                                    cimg_instance);
+
+      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_instance
+                              "load_inr() : Unknown pixel type defined in file '%s'.",
+                              cimg_instance,
+                              filename?filename:"(FILE*)");
+      }
+      if (!file) cimg::fclose(nfile);
+      return *this;
+    }
+
+    //! Load an image from a EXR file.
+    CImg<T>& load_exr(const char *const filename) {
+      if (!filename)
+        throw CImgArgumentException(_cimg_instance
+                                    "load_exr() : Specified filename is (null).",
+                                    cimg_instance);
+
+#ifndef cimg_use_openexr
+      return load_other(filename);
+#else
+      Imf::RgbaInputFile file(filename);
+      Imath::Box2i dw = file.dataWindow();
+      const int
+        inwidth = dw.max.x - dw.min.x + 1,
+        inheight = dw.max.y - dw.min.y + 1;
+      Imf::Array2D<Imf::Rgba> pixels;
+      pixels.resizeErase(inheight,inwidth);
+      file.setFrameBuffer(&pixels[0][0] - dw.min.x - dw.min.y*inwidth, 1, inwidth);
+      file.readPixels(dw.min.y, dw.max.y);
+      assign(inwidth,inheight,1,4);
+      T *ptr_r = data(0,0,0,0), *ptr_g = data(0,0,0,1), *ptr_b = data(0,0,0,2), *ptr_a = data(0,0,0,3);
+      cimg_forXY(*this,x,y) {
+        *(ptr_r++) = (T)pixels[y][x].r;
+        *(ptr_g++) = (T)pixels[y][x].g;
+        *(ptr_b++) = (T)pixels[y][x].b;
+        *(ptr_a++) = (T)pixels[y][x].a;
+      }
+      return *this;
+#endif
+    }
+
+    static CImg<T> get_load_exr(const char *const filename) {
+      return CImg<T>().load_exr(filename);
+    }
+
+    //! 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(std::FILE *const file) {
+      return _load_pandore(file,0);
+    }
+
+    static CImg<T> get_load_pandore(std::FILE *const file) {
+      return CImg<T>().load_pandore(file);
+    }
+
+    CImg<T>& _load_pandore(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_instance \
+                                   "load_pandore() : Unknown pixel datatype in file '%s'.", \
+                                   cimg_instance, \
+                                   filename?filename:"(FILE*)"); }
+
+      if (!file && !filename)
+        throw CImgArgumentException(_cimg_instance
+                                    "load_pandore() : Specified filename is (null).",
+                                    cimg_instance);
+
+      std::FILE *const nfile = file?file:cimg::fopen(filename,"rb");
+      char header[32] = { 0 };
+      cimg::fread(header,12,nfile);
+      if (cimg::strncasecmp("PANDORE",header,7)) {
+        if (!file) cimg::fclose(nfile);
+        throw CImgIOException(_cimg_instance
+                              "load_pandore() : PANDORE header not found in file '%s'.",
+                              cimg_instance,
+                              filename?filename:"(FILE*)");
+      }
+      unsigned int imageid, dims[8] = { 0 };
+      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,unsigned char,unsigned char,unsigned char,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,unsigned char,unsigned char,unsigned char,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,unsigned char,unsigned char,unsigned char,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,unsigned char,unsigned char,unsigned char,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,unsigned char,unsigned char,unsigned char,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],unsigned char,unsigned char,unsigned char,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],unsigned long,unsigned int,unsigned short,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],unsigned char,unsigned char,unsigned char,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],unsigned long,unsigned int,unsigned short,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],unsigned char,unsigned char,unsigned char,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],unsigned long,unsigned int,unsigned short,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] = { 0 };
+        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] = { 0 };
+        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] = { 0 };
+        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_instance
+                              "load_pandore() : Unable to load data with ID_type %u in file '%s'.",
+                              cimg_instance,
+                              imageid,filename?filename:"(FILE*)");
+      }
+      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='c', const float align=0) {
+      CImgList<T> list;
+      list.load_parrec(filename);
+      if (list._width==1) return list[0].move_to(*this);
+      return assign(list.get_append(axis,align));
+    }
+
+    static CImg<T> get_load_parrec(const char *const filename, const char axis='c', const float align=0) {
+      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=0, 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=0, 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(std::FILE *const file,
+                      const unsigned int sizex=0, 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(std::FILE *const file,
+                                const unsigned int sizex=0, 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(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 (!file && !filename)
+        throw CImgArgumentException(_cimg_instance
+                                    "load_raw() : Specified filename is (null).",
+                                    cimg_instance);
+      unsigned int siz = sizex*sizey*sizez*sizev, _sizex = sizex, _sizey = sizey, _sizez = sizez, _sizev = sizev;
+      std::FILE *const nfile = file?file:cimg::fopen(filename,"rb");
+      if (!siz) {  // Retrieve file size.
+        const long fpos = std::ftell(nfile);
+        std::fseek(nfile,0,SEEK_END);
+        siz = _sizey = (unsigned int)std::ftell(nfile)/sizeof(T);
+        _sizex = _sizez = _sizev = 1;
+        std::fseek(nfile,fpos,SEEK_SET);
+      }
+      assign(_sizex,_sizey,_sizez,_sizev,0);
+      if (!multiplexed || sizev==1) {
+        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 float align=0) {
+      return get_load_ffmpeg(filename,first_frame,last_frame,step_frame,pixel_format,resume,axis,align).move_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 float align=0) {
+      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 float align=0) {
+      return get_load_yuv(filename,sizex,sizey,first_frame,last_frame,step_frame,yuv2rgb,axis,align).move_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 float align=0) {
+      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(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 float align=0) {
+      return get_load_yuv(file,sizex,sizey,first_frame,last_frame,step_frame,yuv2rgb,axis,align).move_to(*this);
+    }
+
+    static CImg<T> get_load_yuv(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 float align=0) {
+      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) {
+      return _load_off(0,filename,primitives,colors);
+    }
+
+    template<typename tf, typename tc>
+    static CImg<T> get_load_off(const char *const filename, CImgList<tf>& primitives, CImgList<tc>& colors) {
+      return CImg<T>().load_off(filename,primitives,colors);
+    }
+
+    //! Load a 3d object from a .OFF file.
+    template<typename tf, typename tc>
+    CImg<T>& load_off(std::FILE *const file, CImgList<tf>& primitives, CImgList<tc>& colors) {
+      return _load_off(file,0,primitives,colors);
+    }
+
+    template<typename tf, typename tc>
+    static CImg<T> get_load_off(std::FILE *const file, CImgList<tf>& primitives, CImgList<tc>& colors) {
+      return CImg<T>().load_off(file,primitives,colors);
+    }
+
+    template<typename tf, typename tc>
+    CImg<T>& _load_off(std::FILE *const file, const char *const filename,
+                       CImgList<tf>& primitives, CImgList<tc>& colors) {
+      if (!file && !filename)
+        throw CImgArgumentException(_cimg_instance
+                                    "load_off() : Specified filename is (null).",
+                                    cimg_instance);
+
+      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 = std::fscanf(nfile,"%255[^\n] ",line); } while (!err || (err==1 && *line=='#'));
+      if (cimg::strncasecmp(line,"OFF",3) && cimg::strncasecmp(line,"COFF",4)) {
+        if (!file) cimg::fclose(nfile);
+        throw CImgIOException(_cimg_instance
+                              "load_off() : OFF header not found in file '%s'.",
+                              cimg_instance,
+                              filename?filename:"(FILE*)");
+      }
+      do { err = std::fscanf(nfile,"%255[^\n] ",line); } while (!err || (err==1 && *line=='#'));
+      if ((err = std::sscanf(line,"%u%u%*[^\n] ",&nb_points,&nb_primitives))!=2) {
+        if (!file) cimg::fclose(nfile);
+        throw CImgIOException(_cimg_instance
+                              "load_off() : Invalid number of vertices or primitives specified in file '%s'.",
+                              cimg_instance,
+                              filename?filename:"(FILE*)");
+      }
+
+      // Read points data
+      assign(nb_points,3);
+      float X = 0, Y = 0, Z = 0;
+      cimg_forX(*this,l) {
+        do { err = std::fscanf(nfile,"%255[^\n] ",line); } while (!err || (err==1 && *line=='#'));
+        if ((err = std::sscanf(line,"%f%f%f%*[^\n] ",&X,&Y,&Z))!=3) {
+          if (!file) cimg::fclose(nfile);
+          throw CImgIOException(_cimg_instance
+                                "load_off() : Failed to read vertex %u/%u in file '%s'.",
+                                cimg_instance,
+                                l+1,nb_points,filename?filename:"(FILE*)");
+        }
+        (*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;
+        if ((err = std::fscanf(nfile,"%u",&prim))!=1) stopflag=true;
+        else {
+          ++nb_read;
+          switch (prim) {
+          case 1 : {
+            if ((err = std::fscanf(nfile,"%u%255[^\n] ",&i0,line))<2) {
+              cimg::warn(_cimg_instance
+                         "load_off() : Failed to read primitive %u/%u from file '%s'.",
+                         cimg_instance,
+                         nb_read,nb_primitives,filename?filename:"(FILE*)");
+
+              err = std::fscanf(nfile,"%*[^\n] ");
+            } else {
+              err = std::sscanf(line,"%f%f%f",&c0,&c1,&c2);
+              CImg<tf>::vector(i0).move_to(primitives);
+              CImg<tc>::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255)).move_to(colors);
+            }
+          } break;
+          case 2 : {
+            if ((err = std::fscanf(nfile,"%u%u%255[^\n] ",&i0,&i1,line))<2) {
+              cimg::warn(_cimg_instance
+                         "load_off() : Failed to read primitive %u/%u from file '%s'.",
+                         cimg_instance,
+                         nb_read,nb_primitives,filename?filename:"(FILE*)");
+
+              err = std::fscanf(nfile,"%*[^\n] ");
+            } else {
+              err = std::sscanf(line,"%f%f%f",&c0,&c1,&c2);
+              CImg<tf>::vector(i0,i1).move_to(primitives);
+              CImg<tc>::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255)).move_to(colors);
+            }
+          } break;
+          case 3 : {
+            if ((err = std::fscanf(nfile,"%u%u%u%255[^\n] ",&i0,&i1,&i2,line))<3) {
+              cimg::warn(_cimg_instance
+                         "load_off() : Failed to read primitive %u/%u from file '%s'.",
+                         cimg_instance,
+                         nb_read,nb_primitives,filename?filename:"(FILE*)");
+
+              err = std::fscanf(nfile,"%*[^\n] ");
+            } else {
+              err = std::sscanf(line,"%f%f%f",&c0,&c1,&c2);
+              CImg<tf>::vector(i0,i2,i1).move_to(primitives);
+              CImg<tc>::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255)).move_to(colors);
+            }
+          } break;
+          case 4 : {
+            if ((err = std::fscanf(nfile,"%u%u%u%u%255[^\n] ",&i0,&i1,&i2,&i3,line))<4) {
+              cimg::warn(_cimg_instance
+                         "load_off() : Failed to read primitive %u/%u from file '%s'.",
+                         cimg_instance,
+                         nb_read,nb_primitives,filename?filename:"(FILE*)");
+
+              err = std::fscanf(nfile,"%*[^\n] ");
+            } else {
+              err = std::sscanf(line,"%f%f%f",&c0,&c1,&c2);
+              CImg<tf>::vector(i0,i3,i2,i1).move_to(primitives);
+              CImg<tc>::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255)).move_to(colors);
+            }
+          } break;
+          case 5 : {
+            if ((err = std::fscanf(nfile,"%u%u%u%u%u%255[^\n] ",&i0,&i1,&i2,&i3,&i4,line))<5) {
+              cimg::warn(_cimg_instance
+                         "load_off() : Failed to read primitive %u/%u from file '%s'.",
+                         cimg_instance,
+                         nb_read,nb_primitives,filename?filename:"(FILE*)");
+
+              err = std::fscanf(nfile,"%*[^\n] ");
+            } else {
+              err = std::sscanf(line,"%f%f%f",&c0,&c1,&c2);
+              CImg<tf>::vector(i0,i3,i2,i1).move_to(primitives);
+              CImg<tf>::vector(i0,i4,i3).move_to(primitives);
+              colors.insert(2,CImg<tc>::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255)));
+              ++nb_primitives;
+            }
+          } break;
+          case 6 : {
+            if ((err = std::fscanf(nfile,"%u%u%u%u%u%u%255[^\n] ",&i0,&i1,&i2,&i3,&i4,&i5,line))<6) {
+              cimg::warn(_cimg_instance
+                         "load_off() : Failed to read primitive %u/%u from file '%s'.",
+                         cimg_instance,
+                         nb_read,nb_primitives,filename?filename:"(FILE*)");
+
+              err = std::fscanf(nfile,"%*[^\n] ");
+            } else {
+              err = std::sscanf(line,"%f%f%f",&c0,&c1,&c2);
+              CImg<tf>::vector(i0,i3,i2,i1).move_to(primitives);
+              CImg<tf>::vector(i0,i5,i4,i3).move_to(primitives);
+              colors.insert(2,CImg<tc>::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255)));
+              ++nb_primitives;
+            }
+          } break;
+          case 7 : {
+            if ((err = std::fscanf(nfile,"%u%u%u%u%u%u%u%255[^\n] ",&i0,&i1,&i2,&i3,&i4,&i5,&i6,line))<7) {
+              cimg::warn(_cimg_instance
+                         "load_off() : Failed to read primitive %u/%u from file '%s'.",
+                         cimg_instance,
+                         nb_read,nb_primitives,filename?filename:"(FILE*)");
+
+              err = std::fscanf(nfile,"%*[^\n] ");
+            } else {
+              err = std::sscanf(line,"%f%f%f",&c0,&c1,&c2);
+              CImg<tf>::vector(i0,i4,i3,i1).move_to(primitives);
+              CImg<tf>::vector(i0,i6,i5,i4).move_to(primitives);
+              CImg<tf>::vector(i3,i2,i1).move_to(primitives);
+              colors.insert(3,CImg<tc>::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255)));
+              ++(++nb_primitives);
+            }
+          } break;
+          case 8 : {
+            if ((err = 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_instance
+                         "load_off() : Failed to read primitive %u/%u from file '%s'.",
+                         cimg_instance,
+                         nb_read,nb_primitives,filename?filename:"(FILE*)");
+
+              err = std::fscanf(nfile,"%*[^\n] ");
+            } else {
+              err = std::sscanf(line,"%f%f%f",&c0,&c1,&c2);
+              CImg<tf>::vector(i0,i3,i2,i1).move_to(primitives);
+              CImg<tf>::vector(i0,i5,i4,i3).move_to(primitives);
+              CImg<tf>::vector(i0,i7,i6,i5).move_to(primitives);
+              colors.insert(3,CImg<tc>::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255)));
+              ++(++nb_primitives);
+            }
+          } break;
+          default :
+            cimg::warn(_cimg_instance
+                       "load_off() : Failed to read primitive %u/%u (%u vertices) from file '%s'.",
+                       cimg_instance,
+                       nb_read,nb_primitives,prim,filename?filename:"(FILE*)");
+
+            err = std::fscanf(nfile,"%*[^\n] ");
+          }
+        }
+      }
+      if (!file) cimg::fclose(nfile);
+      if (primitives._width!=nb_primitives)
+        cimg::warn(_cimg_instance
+                   "load_off() : Only %u/%u primitives read from file '%s'.",
+                   cimg_instance,
+                   primitives._width,nb_primitives,filename?filename:"(FILE*)");
+      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 float align=0) {
+      return get_load_ffmpeg_external(filename,axis,align).move_to(*this);
+    }
+
+    static CImg<T> get_load_ffmpeg_external(const char *const filename, const char axis='z', const float align=0) {
+      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_instance
+                                    "load_graphicsmagick_external() : Specified filename is (null).",
+                                    cimg_instance);
+      std::fclose(cimg::fopen(filename,"rb"));            // Check if file exists.
+      char command[1024] = { 0 }, filetmp[512] = { 0 };
+      std::FILE *file = 0;
+#if cimg_OS==1
+      cimg_snprintf(command,sizeof(command),"%s convert \"%s\" pnm:-",cimg::graphicsmagick_path(),filename);
+      file = popen(command,"r");
+      if (file) {
+        try { load_pnm(file); } catch (...) {
+          pclose(file);
+          throw CImgIOException(_cimg_instance
+                                "load_graphicsmagick_external() : Failed to load file '%s' with external command 'gm'.",
+                                cimg_instance,
+                                filename);
+        }
+        pclose(file);
+        return *this;
+      }
+#endif
+      do {
+        cimg_snprintf(filetmp,sizeof(filetmp),"%s%c%s.pnm",cimg::temporary_path(),cimg_file_separator,cimg::filenamerand());
+        if ((file=std::fopen(filetmp,"rb"))!=0) cimg::fclose(file);
+      } while (file);
+      cimg_snprintf(command,sizeof(command),"%s convert \"%s\" \"%s\"",cimg::graphicsmagick_path(),filename,filetmp);
+      cimg::system(command,cimg::graphicsmagick_path());
+      if (!(file = std::fopen(filetmp,"rb"))) {
+        cimg::fclose(cimg::fopen(filename,"r"));
+        throw CImgIOException(_cimg_instance
+                              "load_graphicsmagick_external() : Failed to load file '%s' with external command 'gm'.",
+                              cimg_instance,
+                              filename);
+
+      } else cimg::fclose(file);
+      load_pnm(filetmp);
+      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_instance
+                              "load_gzip_external() : Specified filename is (null).",
+                              cimg_instance);
+      std::fclose(cimg::fopen(filename,"rb"));            // Check if file exists.
+      char command[1024] = { 0 }, filetmp[512] = { 0 }, body[512] = { 0 };
+      const char
+        *const ext = cimg::split_filename(filename,body),
+        *const ext2 = cimg::split_filename(body,0);
+
+      std::FILE *file = 0;
+      do {
+        if (!cimg::strcasecmp(ext,"gz")) {
+          if (*ext2) cimg_snprintf(filetmp,sizeof(filetmp),"%s%c%s.%s",cimg::temporary_path(),cimg_file_separator,cimg::filenamerand(),ext2);
+          else cimg_snprintf(filetmp,sizeof(filetmp),"%s%c%s",cimg::temporary_path(),cimg_file_separator,cimg::filenamerand());
+        } else {
+          if (*ext) cimg_snprintf(filetmp,sizeof(filetmp),"%s%c%s.%s",cimg::temporary_path(),cimg_file_separator,cimg::filenamerand(),ext);
+          else cimg_snprintf(filetmp,sizeof(filetmp),"%s%c%s",cimg::temporary_path(),cimg_file_separator,cimg::filenamerand());
+        }
+        if ((file=std::fopen(filetmp,"rb"))!=0) cimg::fclose(file);
+      } while (file);
+
+      cimg_snprintf(command,sizeof(command),"%s -c \"%s\" > %s",cimg::gunzip_path(),filename,filetmp);
+      cimg::system(command);
+      if (!(file = std::fopen(filetmp,"rb"))) {
+        cimg::fclose(cimg::fopen(filename,"r"));
+        throw CImgIOException(_cimg_instance
+                              "load_gzip_external() : Failed to load file '%s' with external command 'gunzip'.",
+                              cimg_instance,
+                              filename);
+
+      } else cimg::fclose(file);
+      load(filetmp);
+      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_instance
+                                    "load_imagemagick_external() : Specified filename is (null).",
+                                    cimg_instance);
+      std::fclose(cimg::fopen(filename,"rb"));            // Check if file exists.
+      char command[1024] = { 0 }, filetmp[512] = { 0 };
+      std::FILE *file = 0;
+#if cimg_OS==1
+      cimg_snprintf(command,sizeof(command),"%s \"%s\" pnm:-",cimg::imagemagick_path(),filename);
+      file = popen(command,"r");
+      if (file) {
+        try { load_pnm(file); } catch (...) {
+          pclose(file);
+          throw CImgIOException(_cimg_instance
+                                "load_imagemagick_external() : Failed to load file '%s' with external command 'convert'.",
+                                cimg_instance,
+                                filename);
+        }
+        pclose(file);
+        return *this;
+      }
+#endif
+      do {
+        cimg_snprintf(filetmp,sizeof(filetmp),"%s%c%s.pnm",cimg::temporary_path(),cimg_file_separator,cimg::filenamerand());
+        if ((file=std::fopen(filetmp,"rb"))!=0) cimg::fclose(file);
+      } while (file);
+      cimg_snprintf(command,sizeof(command),"%s \"%s\" \"%s\"",cimg::imagemagick_path(),filename,filetmp);
+      cimg::system(command,cimg::imagemagick_path());
+      if (!(file = std::fopen(filetmp,"rb"))) {
+        cimg::fclose(cimg::fopen(filename,"r"));
+        throw CImgIOException(_cimg_instance
+                              "load_imagemagick_external() : Failed to load file '%s' with external command 'convert'.",
+                              cimg_instance,
+                              filename);
+
+      } else cimg::fclose(file);
+      load_pnm(filetmp);
+      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_instance
+                                    "load_medcon_external() : Specified filename is (null).",
+                                    cimg_instance);
+      std::fclose(cimg::fopen(filename,"rb"));            // Check if file exists.
+      char command[1024] = { 0 }, filetmp[512] = { 0 }, body[512] = { 0 };
+      cimg::fclose(cimg::fopen(filename,"r"));
+      std::FILE *file = 0;
+      do {
+        cimg_snprintf(filetmp,sizeof(filetmp),"%s.hdr",cimg::filenamerand());
+        if ((file=std::fopen(filetmp,"rb"))!=0) cimg::fclose(file);
+      } while (file);
+      cimg_snprintf(command,sizeof(command),"%s -w -c anlz -o %s -f %s",cimg::medcon_path(),filetmp,filename);
+      cimg::system(command);
+      cimg::split_filename(filetmp,body);
+
+      cimg_snprintf(command,sizeof(command),"%s.hdr",body);
+      file = std::fopen(command,"rb");
+      if (!file) {
+        cimg_snprintf(command,sizeof(command),"m000-%s.hdr",body);
+        file = std::fopen(command,"rb");
+        if (!file) {
+          throw CImgIOException(_cimg_instance
+                                "load_medcon_external() : Failed to load file '%s' with external command 'medcon'.",
+                                cimg_instance,
+                                filename);
+        }
+      }
+      cimg::fclose(file);
+      load_analyze(command);
+      std::remove(command);
+      cimg::split_filename(command,body);
+      cimg_snprintf(command,sizeof(command),"%s.img",body);
+      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_instance
+                                    "load_dcraw_external() : Specified filename is (null).",
+                                    cimg_instance);
+      std::fclose(cimg::fopen(filename,"rb"));            // Check if file exists.
+      char command[1024] = { 0 }, filetmp[512] = { 0 };
+      std::FILE *file = 0;
+#if cimg_OS==1
+      cimg_snprintf(command,sizeof(command),"%s -w -4 -c \"%s\"",cimg::dcraw_path(),filename);
+      file = popen(command,"r");
+      if (file) {
+        try { load_pnm(file); } catch (...) {
+          pclose(file);
+          throw CImgIOException(_cimg_instance
+                                "load_dcraw_external() : Failed to load file '%s' with external command 'dcraw'.",
+                                cimg_instance,
+                                filename);
+        }
+        pclose(file);
+        return *this;
+      }
+#endif
+      do {
+        cimg_snprintf(filetmp,sizeof(filetmp),"%s%c%s.ppm",cimg::temporary_path(),cimg_file_separator,cimg::filenamerand());
+        if ((file=std::fopen(filetmp,"rb"))!=0) cimg::fclose(file);
+      } while (file);
+      cimg_snprintf(command,sizeof(command),"%s -w -4 -c \"%s\" > %s",cimg::dcraw_path(),filename,filetmp);
+      cimg::system(command,cimg::dcraw_path());
+      if (!(file = std::fopen(filetmp,"rb"))) {
+        cimg::fclose(cimg::fopen(filename,"r"));
+        throw CImgIOException(_cimg_instance
+                              "load_dcraw_external() : Failed to load file '%s' with external command 'dcraw'.",
+                              cimg_instance,
+                              filename);
+
+      } else cimg::fclose(file);
+      load_pnm(filetmp);
+      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 from a camera stream, using OpenCV.
+    CImg<T>& load_camera(const int camera_index=-1, const unsigned int skip_frames=0, const bool release_camera=false) {
+#ifdef cimg_use_opencv
+      const int ind = camera_index + 1;
+      if (ind<0 || ind>255)
+        throw CImgArgumentException(_cimg_instance
+                                    "load_camera() : Invalid request for camera #%d.",
+                                    cimg_instance,
+                                    camera_index);
+      static CvCapture *capture[256] = { 0 };
+      if (release_camera) {
+        if (capture[ind]) cvReleaseCapture(&(capture[ind]));
+        capture[ind] = 0;
+        return *this;
+      }
+      if (!capture[ind]) {
+        capture[ind] = cvCreateCameraCapture(camera_index);
+        if (!capture[ind]) {
+          if (camera_index>=0)
+            throw CImgIOException(_cimg_instance
+                                  "load_camera() : Failed to initialize camera #%d.",
+                                  cimg_instance,
+                                  camera_index);
+          else
+            throw CImgIOException(_cimg_instance
+                                  "load_camera() : Failed to initialize default camera.",
+                                  cimg_instance);
+        }
+      }
+      const IplImage *img = 0;
+      for (unsigned int i = 0; i<skip_frames; ++i) img = cvQueryFrame(capture[ind]);
+      img = cvQueryFrame(capture[ind]);
+      if (img) {
+        const int step = (int)(img->widthStep - 3*img->width);
+        assign(img->width,img->height,1,3);
+        const unsigned char* ptrs = (unsigned char*)img->imageData;
+        T *ptr_r = data(0,0,0,0), *ptr_g = data(0,0,0,1), *ptr_b = data(0,0,0,2);
+        if (step>0) cimg_forY(*this,y) {
+            cimg_forX(*this,x) { *(ptr_b++) = (T)*(ptrs++); *(ptr_g++) = (T)*(ptrs++); *(ptr_r++) = (T)*(ptrs++); }
+            ptrs+=step;
+          } else for (unsigned int siz = img->width*img->height; siz; --siz) { *(ptr_b++) = (T)*(ptrs++); *(ptr_g++) = (T)*(ptrs++); *(ptr_r++) = (T)*(ptrs++); }
+      }
+#else
+      cimg::unused(camera_index,skip_frames,release_camera);
+      throw CImgIOException(_cimg_instance
+                            "load_camera() : This function requires the OpenCV library to run "
+                            "(macro 'cimg_use_opencv' must be defined).",
+                            cimg_instance);
+#endif
+      return *this;
+    }
+
+    static CImg<T> get_load_camera(const int camera_index=-1, const unsigned int skip_frames=0, const bool release_camera=false) {
+      return CImg<T>().load_camera(camera_index,skip_frames,release_camera);
+    }
+
+    //! Load an image using ImageMagick's or GraphicsMagick's executables. If failed, try to load a .cimg[z] file format.
+    CImg<T>& load_other(const char *const filename) {
+      if (!filename)
+        throw CImgArgumentException(_cimg_instance
+                                    "load_other() : Specified filename is (null).",
+                                    cimg_instance);
+
+      const unsigned int omode = 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&) {
+            try { load_cimg(filename); }
+            catch (CImgException&) {
+              try {
+                std::fclose(cimg::fopen(filename,"rb"));
+                cimg::exception_mode() = omode;
+                throw CImgIOException(_cimg_instance
+                                      "load_other() : Failed to recognize format of file '%s'.",
+                                      cimg_instance,
+                                      filename);
+              } catch (CImgException&) {
+                cimg::exception_mode() = omode;
+                throw CImgIOException(_cimg_instance
+                                      "load_other() : Failed to open file '%s'.",
+                                      cimg_instance,
+                                      filename);
+              }
+            }
+          }
+        }
+      }
+      cimg::exception_mode() = omode;
+      return *this;
+    }
+
+    static CImg<T> get_load_other(const char *const filename) {
+      return CImg<T>().load_other(filename);
+    }
+
+    //@}
+    //---------------------------
+    //
+    //! \name Data Output
+    //@{
+    //---------------------------
+
+    //! 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 *const 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 int 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 _title[64] = { 0 };
+      if (!title) cimg_snprintf(_title,sizeof(_title),"CImg<%s>",pixel_type());
+
+      std::fprintf(cimg::output(),"%s%s%s%s : %sthis%s = %p, %ssize%s = (%u,%u,%u,%u) [%u %s], %sdata%s = (%s*)%p",
+                   cimg::t_magenta,cimg::t_bold,title?title:_title,cimg::t_normal,
+                   cimg::t_bold,cimg::t_normal,(void*)this,
+                   cimg::t_bold,cimg::t_normal,_width,_height,_depth,_spectrum,
+                   mdisp==0?msiz:(mdisp==1?(msiz>>10):(msiz>>20)),
+                   mdisp==0?"b":(mdisp==1?"Kb":"Mb"),
+                   cimg::t_bold,cimg::t_normal,pixel_type(),(void*)begin());
+      if (_data) std::fprintf(cimg::output(),"..%p (%s) = [ ",(void*)((char*)end()-1),_is_shared?"shared":"non-shared");
+      else std::fprintf(cimg::output()," (%s) = [ ",_is_shared?"shared":"non-shared");
+
+      if (!is_empty()) cimg_foroff(*this,off) {
+        std::fprintf(cimg::output(),cimg::type<T>::format(),cimg::type<T>::format(_data[off]));
+        if (off!=siz1) std::fprintf(cimg::output(),"%s",off%_width==width1?" ; ":" ");
+        if (off==7 && siz>16) { off = siz1-8; if (off!=7) std::fprintf(cimg::output(),"... "); }
+      }
+      if (!is_empty() && display_stats)
+        std::fprintf(cimg::output()," ], %smin%s = %g, %smax%s = %g, %smean%s = %g, %sstd%s = %g, %scoords_min%s = (%u,%u,%u,%u), %scoords_max%s = (%u,%u,%u,%u).\n",
+                     cimg::t_bold,cimg::t_normal,st[0],
+                     cimg::t_bold,cimg::t_normal,st[1],
+                     cimg::t_bold,cimg::t_normal,st[2],
+                     cimg::t_bold,cimg::t_normal,std::sqrt(st[3]),
+                     cimg::t_bold,cimg::t_normal,xm,ym,zm,vm,
+                     cimg::t_bold,cimg::t_normal,xM,yM,zM,vM);
+      else std::fprintf(cimg::output(),"%s].\n",is_empty()?"":" ");
+      std::fflush(cimg::output());
+      return *this;
+    }
+
+    //! 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,false);
+    }
+
+    //! 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,false);
+    }
+
+    const CImg<T>& _display(CImgDisplay &disp, const char *const title,
+                            const bool display_info, const bool exit_on_simpleclick) const {
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "display() : Empty instance.",
+                                    cimg_instance);
+
+      unsigned int oldw = 0, oldh = 0, XYZ[3], key = 0;
+      int x0 = 0, y0 = 0, z0 = 0, x1 = width() - 1, y1 = height() - 1, z1 = depth() - 1;
+
+      if (!disp) {
+        disp.assign(cimg_fitscreen(_width,_height,_depth),title?title:0,1);
+        if (!title) disp.set_title("CImg<%s> (%ux%ux%ux%u)",pixel_type(),_width,_height,_depth,_spectrum);
+      } else if (title) disp.set_title("%s",title);
+      disp.show().flush();
+
+      const CImg<char> dtitle = CImg<char>::string(disp.title());
+      if (display_info) print(dtitle);
+
+      CImg<T> zoom;
+      for (bool reset_view = true, resize_disp = false, is_first_select = true; !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==width()-1 && y1==height()-1 && z1==depth()-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 (!disp.is_fullscreen() && resize_disp) {
+          const unsigned int
+            ttw = tw*disp.width()/oldw, tth = th*disp.height()/oldh,
+            dM = cimg::max(ttw,tth), diM = (unsigned int)cimg::max(disp.width(),disp.height()),
+            imgw = cimg::max(16U,ttw*diM/dM), imgh = cimg::max(16U,tth*diM/dM);
+          disp.set_fullscreen(false).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,x0,y0,z0,is_first_select);
+        is_first_select = false;
+
+        if (disp.wheel()) {
+          if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { go_out = !(go_in = disp.wheel()>0); go_in_center = false; }
+          else if (disp.is_keySHIFTLEFT() || disp.is_keySHIFTRIGHT()) { go_right = !(go_left = disp.wheel()>0); }
+          else if (disp.is_keyALT() || disp.is_keyALTGR() || _depth==1) { go_down = !(go_up = disp.wheel()>0); }
+          disp.set_wheel();
+        }
+
+        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) {
+            if (exit_on_simpleclick && !zoom) break; else reset_view = true;
+          }
+          resize_disp = true;
+        } else switch (key = disp.key()) {
+#if cimg_OS!=2
+          case cimg::keyCTRLRIGHT : case cimg::keySHIFTRIGHT :
+#endif
+          case 0 : case cimg::keyCTRLLEFT : case cimg::keyPAD5 : case cimg::keySHIFTLEFT :
+#if cimg_OS!=2
+          case cimg::keyALTGR :
+#endif
+          case cimg::keyALT : key = 0; break;
+          case cimg::keyP : if (visu._depth>1 && (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT())) { // 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));
+              float frame_timing = 5;
+              bool is_stopped = false;
+              disp.set_key(key,false).set_wheel().resize(cimg_fitscreen(w1,h1,1),false); key = 0;
+              for (unsigned int timer = 0; !key && !disp.is_closed() && !disp.button(); ) {
+                if (disp.is_resized()) disp.resize(false);
+                if (!timer) {
+                  visu.get_slice((int)XYZ[2]).display(disp.set_title("%s | z=%d",dtitle.data(),XYZ[2]));
+                  (++XYZ[2])%=visu._depth;
+                }
+                if (!is_stopped) { if (++timer>(unsigned int)frame_timing) timer = 0; } else timer = ~0U;
+                if (disp.wheel()) { frame_timing-=disp.wheel()/3.0f; disp.set_wheel(); }
+                switch (key = disp.key()) {
+#if cimg_OS!=2
+                case cimg::keyCTRLRIGHT :
+#endif
+                case cimg::keyCTRLLEFT : key = 0; break;
+                case cimg::keyPAGEUP : frame_timing-=0.3f; key = 0; break;
+                case cimg::keyPAGEDOWN : frame_timing+=0.3f; key = 0; break;
+                case cimg::keySPACE : is_stopped = !is_stopped; disp.set_key(key,false); key = 0; break;
+                case cimg::keyARROWLEFT : case cimg::keyARROWUP : is_stopped = true; timer = 0; key = 0; break;
+                case cimg::keyARROWRIGHT : case cimg::keyARROWDOWN : is_stopped = true; (XYZ[2]+=visu._depth-2)%=visu._depth; timer = 0; key = 0; break;
+                case cimg::keyD : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) {
+                    disp.set_fullscreen(false).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.set_key(key,false); key = 0;
+                  } break;
+                case cimg::keyC : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) {
+                    disp.set_fullscreen(false).resize(cimg_fitscreen(2*disp.width()/3,2*disp.height()/3,1),false).set_key(key,false); key = 0;
+                  } break;
+                case cimg::keyR : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) {
+                    disp.set_fullscreen(false).resize(cimg_fitscreen(_width,_height,_depth),false).set_key(key,false); key = 0;
+                  } break;
+                case cimg::keyF : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) {
+                    disp.resize(disp.screen_width(),disp.screen_height(),false).toggle_fullscreen().set_key(key,false); key = 0;
+                  } break;
+                }
+                frame_timing = frame_timing<1?1:(frame_timing>39?39:frame_timing);
+                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(dtitle.data()).set_key().set_button().set_wheel();
+              key = 0;
+            } break;
+          case cimg::keyHOME : 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.width()/2:disp.mouse_x(),
+            my = go_in_center?disp.height()/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<width() && mY<height())  { X = x0 + mX*(1+x1-x0)/_width; Y = y0 + mY*(1+y1-y0)/_height; Z = XYZ[2]; }
+          if (mX<width() && mY>=height()) { X = x0 + mX*(1+x1-x0)/_width; Z = z0 + (mY-_height)*(1+z1-z0)/_depth; Y = XYZ[1]; }
+          if (mX>=width() && mY<height()) { 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>=width()) x1 = width() - 1; }
+          if (y0<0) { y1-=y0; y0 = 0; if (y1>=height()) y1 = height() - 1; }
+          if (z0<0) { z1-=z0; z0 = 0; if (z1>=depth()) z1 = depth() - 1; }
+          if (x1>=width()) { x0-=(x1-width()+1); x1 = width()-1; if (x0<0) x0 = 0; }
+          if (y1>=height()) { y0-=(y1-height()+1); y1 = height()-1; if (y0<0) y0 = 0; }
+          if (z1>=depth()) { z0-=(z1-depth()+1); z1 = depth()-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<width()) { x0+=ndelta; x1+=ndelta; }
+          else { x0+=(width()-1-x1); x1 = width()-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<height()) { y0+=ndelta; y1+=ndelta; }
+          else { y0+=(height()-1-y1); y1 = height()-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<depth()) { z0+=ndelta; z1+=ndelta; }
+          else { z0+=(depth()-1-z1); z1 = depth()-1; }
+        }
+      }
+      disp.set_key(key);
+      return *this;
+    }
+
+    //! 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>& vertices,
+                                    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=true, const float focale=500,
+                                    const float light_x=0, const float light_y=0, const float light_z=-5000,
+                                    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,vertices,primitives,colors,opacities,centering,render_static,
+                               render_motion,double_sided,focale,
+                               light_x,light_y,light_z,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>& vertices,
+                                    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=true, const float focale=500,
+                                    const float light_x=0, const float light_y=0, const float light_z=-5000,
+                                    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,vertices,primitives,colors,opacities,centering,render_static,
+                               render_motion,double_sided,focale,
+                               light_x,light_y,light_z,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 CImg<tp>& vertices,
+                                    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=true, const float focale=500,
+                                    const float light_x=0, const float light_y=0, const float light_z=-5000,
+                                    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,vertices,primitives,colors,CImgList<floatT>(),centering,
+                              render_static,render_motion,double_sided,focale,
+                              light_x,light_y,light_z,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 CImg<tp>& vertices,
+                                    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=true, const float focale=500,
+                                    const float light_x=0, const float light_y=0, const float light_z=-5000,
+                                    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,vertices,primitives,colors,CImgList<floatT>(),centering,
+                              render_static,render_motion,double_sided,focale,
+                              light_x,light_y,light_z,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 CImg<tp>& vertices,
+                                    const CImgList<tf>& primitives,
+                                    const bool centering=true,
+                                    const int render_static=4, const int render_motion=1,
+                                    const bool double_sided=true, const float focale=500,
+                                    const float light_x=0, const float light_y=0, const float light_z=-5000,
+                                    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,vertices,primitives,CImgList<T>(),centering,
+                              render_static,render_motion,double_sided,focale,
+                              light_x,light_y,light_z,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 CImg<tp>& vertices,
+                                    const CImgList<tf>& primitives,
+                                    const bool centering=true,
+                                    const int render_static=4, const int render_motion=1,
+                                    const bool double_sided=true, const float focale=500,
+                                    const float light_x=0, const float light_y=0, const float light_z=-5000,
+                                    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,vertices,primitives,CImgList<T>(),centering,
+                              render_static,render_motion,double_sided,focale,
+                              light_x,light_y,light_z,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 CImg<tp>& vertices,
+                                    const bool centering=true,
+                                    const int render_static=4, const int render_motion=1,
+                                    const bool double_sided=true, const float focale=500,
+                                    const float light_x=0, const float light_y=0, const float light_z=-5000,
+                                    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,vertices,CImgList<uintT>(),centering,
+                              render_static,render_motion,double_sided,focale,
+                              light_x,light_y,light_z,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 CImg<tp>& vertices,
+                                    const bool centering=true,
+                                    const int render_static=4, const int render_motion=1,
+                                    const bool double_sided=true, const float focale=500,
+                                    const float light_x=0, const float light_y=0, const float light_z=-5000,
+                                    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,vertices,CImgList<uintT>(),centering,
+                              render_static,render_motion,double_sided,focale,
+                              light_x,light_y,light_z,specular_light,specular_shine,
+                              display_axes,pose_matrix);
+    }
+
+    template<typename tp, typename tf, typename tc, typename to>
+    const CImg<T>& _display_object3d(CImgDisplay& disp, const char *const title,
+                                     const CImg<tp>& vertices,
+                                     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 light_x, const float light_y, const float light_z,
+                                     const float specular_light, const float specular_shine,
+                                     const bool display_axes, float *const pose_matrix) const {
+      typedef typename cimg::superset<tp,float>::type tpfloat;
+
+      // Check input arguments
+      if (is_empty()) {
+        if (disp) return CImg<T>(disp.width(),disp.height(),1,(colors && colors[0].size()==1)?1:3,0).
+                    _display_object3d(disp,title,vertices,primitives,colors,opacities,centering,
+                                      render_static,render_motion,double_sided,focale,
+                                      light_x,light_y,light_z,specular_light,specular_shine,
+                                      display_axes,pose_matrix);
+        else return CImg<T>(1,2,1,1,64,128).resize(cimg_fitscreen(640,480,1),1,(colors && colors[0].size()==1)?1:3,3).
+               _display_object3d(disp,title,vertices,primitives,colors,opacities,centering,
+                                 render_static,render_motion,double_sided,focale,
+                                 light_x,light_y,light_z,specular_light,specular_shine,
+                                 display_axes,pose_matrix);
+      } else { if (disp) disp.resize(*this,false); }
+      char error_message[1024] = { 0 };
+      if (!vertices.is_object3d(primitives,colors,opacities,true,error_message))
+        throw CImgArgumentException(_cimg_instance
+                                    "display_object3d() : Invalid specified 3d object (%u,%u) (%s).",
+                                    cimg_instance,vertices._width,primitives._width,error_message);
+      if (vertices._width && !primitives) {
+        CImgList<tf> nprimitives(vertices._width,1,1,1,1);
+        cimglist_for(nprimitives,l) nprimitives(l,0) = l;
+        return _display_object3d(disp,title,vertices,nprimitives,colors,opacities,centering,
+                                 render_static,render_motion,double_sided,focale,
+                                 light_x,light_y,light_z,specular_light,specular_shine,
+                                 display_axes,pose_matrix);
+      }
+      if (!disp) {
+        disp.assign(cimg_fitscreen(_width,_height,_depth),title?title:0,3);
+        if (!title) disp.set_title("CImg<%s> (%u vertices, %u primitives)",pixel_type(),vertices._width,primitives._width);
+      } else if (title) disp.set_title("%s",title);
+
+      // Init 3d objects and compute object statistics
+      CImg<floatT>
+        pose,
+        rotated_vertices(vertices._width,3),
+        bbox_vertices, rotated_bbox_vertices,
+        axes_vertices, rotated_axes_vertices,
+        bbox_opacities, axes_opacities;
+      CImgList<uintT> bbox_primitives, axes_primitives;
+      CImgList<tf> reverse_primitives;
+      CImgList<T> bbox_colors, bbox_colors2, axes_colors;
+      unsigned int ns_width = 0, ns_height = 0;
+      int _double_sided = (int)double_sided;
+      bool ndisplay_axes = display_axes;
+      const CImg<T>
+        background_color(1,1,1,_spectrum,0),
+        foreground_color(1,1,1,_spectrum,255);
+      float
+        Xoff = 0, Yoff = 0, Zoff = 0, sprite_scale = 1,
+        xm = 0, xM = vertices?vertices.get_shared_line(0).max_min(xm):0,
+        ym = 0, yM = vertices?vertices.get_shared_line(1).max_min(ym):0,
+        zm = 0, zM = vertices?vertices.get_shared_line(2).max_min(zm):0;
+      const float delta = cimg::max(xM-xm,yM-ym,zM-zm);
+
+      rotated_bbox_vertices = bbox_vertices.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,_spectrum,1,1,1,background_color[0]);
+      bbox_colors2.assign(6,_spectrum,1,1,1,foreground_color[0]);
+      bbox_opacities.assign(bbox_colors._width,1,1,1,0.3f);
+
+      rotated_axes_vertices = axes_vertices.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,_spectrum,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;
+      CImg<tpfloat> zbuffer(visu0.width(),visu0.height(),1,1,0);
+      bool init_pose = true, clicked = false, redraw = true;
+      unsigned int key = 0;
+      int
+        x0 = 0, y0 = 0, x1 = 0, y1 = 0,
+        nrender_static = render_static,
+        nrender_motion = render_motion;
+      disp.show().flush();
+
+      while (!disp.is_closed() && !key) {
+
+        // Init object pose
+        if (init_pose) {
+          const float
+            ratio = delta>0?(2.0f*cimg::min(disp.width(),disp.height())/(3.0f*delta)):1,
+            dx = (xM + xm)/2, dy = (yM + ym)/2, dz = (zM + zm)/2;
+          if (centering)
+            CImg<floatT>(4,3,1,1, ratio,0.,0.,-ratio*dx, 0.,ratio,0.,-ratio*dy, 0.,0.,ratio,-ratio*dz).move_to(pose);
+          else CImg<floatT>(4,3,1,1, 1,0,0,0, 0,1,0,0, 0,0,1,0).move_to(pose);
+          if (pose_matrix) {
+            CImg<floatT> pose0(pose_matrix,4,3,1,1,false);
+            pose0.resize(4,4,1,1,0); pose.resize(4,4,1,1,0);
+            pose0(3,3) = pose(3,3) = 1;
+            (pose0*pose).get_crop(0,0,3,2).move_to(pose);
+            Xoff = pose_matrix[12]; Yoff = pose_matrix[13]; Zoff = pose_matrix[14]; sprite_scale = pose_matrix[15];
+          } else { Xoff = Yoff = Zoff = 0; sprite_scale = 1; }
+          init_pose = 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 && nrender_motion>=0) || (!clicked && nrender_static>=0))
+            cimg_forX(vertices,l) {
+              const float x = (float)vertices(l,0), y = (float)vertices(l,1), z = (float)vertices(l,2);
+              rotated_vertices(l,0) = r00*x + r10*y + r20*z + r30;
+              rotated_vertices(l,1) = r01*x + r11*y + r21*z + r31;
+              rotated_vertices(l,2) = r02*x + r12*y + r22*z + r32;
+            }
+          else cimg_forX(bbox_vertices,l) {
+              const float x = bbox_vertices(l,0), y = bbox_vertices(l,1), z = bbox_vertices(l,2);
+              rotated_bbox_vertices(l,0) = r00*x + r10*y + r20*z + r30;
+              rotated_bbox_vertices(l,1) = r01*x + r11*y + r21*z + r31;
+              rotated_bbox_vertices(l,2) = r02*x + r12*y + r22*z + r32;
+            }
+
+          // Draw object
+          visu = visu0;
+          if ((clicked && nrender_motion<0) || (!clicked && nrender_static<0))
+            visu.draw_object3d(Xoff + visu._width/2.0f,Yoff + visu._height/2.0f,Zoff,
+                               rotated_bbox_vertices,bbox_primitives,bbox_colors,bbox_opacities,2,false,focale).
+              draw_object3d(Xoff + visu._width/2.0f,Yoff + visu._height/2.0f,Zoff,
+                            rotated_bbox_vertices,bbox_primitives,bbox_colors2,1,false,focale);
+          else visu._draw_object3d((void*)0,(!clicked && nrender_static>0)?zbuffer.fill(0):CImg<tpfloat>::empty(),
+                                   Xoff + visu._width/2.0f,Yoff + visu._height/2.0f,Zoff,
+                                   rotated_vertices,reverse_primitives?reverse_primitives:primitives,
+                                   colors,opacities,clicked?nrender_motion:nrender_static,_double_sided==1,focale,
+                                   width()/2.0f+light_x,height()/2.0f+light_y,light_z,specular_light,specular_shine,
+                                   sprite_scale);
+          // Draw axes
+          if (ndisplay_axes) {
+            const float
+              n = (float)std::sqrt(1e-8 + r00*r00 + r01*r01 + r02*r02),
+              _r00 = r00/n, _r10 = r10/n, _r20 = r20/n,
+              _r01 = r01/n, _r11 = r11/n, _r21 = r21/n,
+              _r02 = r01/n, _r12 = r12/n, _r22 = r22/n,
+              Xaxes = 25, Yaxes = visu._height - 38.0f;
+            cimg_forX(axes_vertices,l) {
+              const float
+                x = axes_vertices(l,0),
+                y = axes_vertices(l,1),
+                z = axes_vertices(l,2);
+              rotated_axes_vertices(l,0) = _r00*x + _r10*y + _r20*z;
+              rotated_axes_vertices(l,1) = _r01*x + _r11*y + _r21*z;
+              rotated_axes_vertices(l,2) = _r02*x + _r12*y + _r22*z;
+            }
+            axes_opacities(0,0) = (rotated_axes_vertices(1,2)>0)?0.5f:1.0f;
+            axes_opacities(1,0) = (rotated_axes_vertices(2,2)>0)?0.5f:1.0f;
+            axes_opacities(2,0) = (rotated_axes_vertices(3,2)>0)?0.5f:1.0f;
+            visu.draw_object3d(Xaxes,Yaxes,0,rotated_axes_vertices,axes_primitives,axes_colors,axes_opacities,1,false,focale).
+              draw_text((int)(Xaxes+rotated_axes_vertices(4,0)),
+                        (int)(Yaxes+rotated_axes_vertices(4,1)),
+                        "X",axes_colors[0]._data,0,axes_opacities(0,0),13).
+              draw_text((int)(Xaxes+rotated_axes_vertices(5,0)),
+                        (int)(Yaxes+rotated_axes_vertices(5,1)),
+                        "Y",axes_colors[1]._data,0,axes_opacities(1,0),13).
+              draw_text((int)(Xaxes+rotated_axes_vertices(6,0)),
+                        (int)(Yaxes+rotated_axes_vertices(6,1)),
+                        "Z",axes_colors[2]._data,0,axes_opacities(2,0),13);
+          }
+          visu.display(disp);
+          if (!clicked || nrender_motion==nrender_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.width()/2),
+              v0 = (float)(y0-disp.height()/2),
+              u1 = (float)(x1-disp.width()/2),
+              v1 = (float)(y1-disp.height()/2),
+              n0 = (float)std::sqrt(u0*u0+v0*v0),
+              n1 = (float)std::sqrt(u1*u1+v1*v1),
+              nu0 = n0>R?(u0*R/n0):u0,
+              nv0 = n0>R?(v0*R/n0):v0,
+              nw0 = (float)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)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)std::sqrt(u*u+v*v+w*w),
+              alpha = (float)std::asin(n/R2);
+            (CImg<floatT>::rotation_matrix(u,v,w,alpha)*pose).move_to(pose);
+            x0 = x1; y0 = y1;
+          }
+          if (disp.button()&2) {
+            if (focale>0) Zoff-=(y0-y1)*focale/400;
+            else { const float s = std::exp((y0-y1)/400.0f); pose*=s; sprite_scale*=s; }
+            x0 = x1; y0 = y1;
+          }
+          if (disp.wheel()) {
+            if (focale>0) Zoff-=disp.wheel()*focale/20;
+            else { const float s = std::exp(disp.wheel()/20.0f); pose*=s; sprite_scale*=s; }
+            disp.set_wheel();
+          }
+          if (disp.button()&4) { Xoff+=(x1-x0); Yoff+=(y1-y0); x0 = x1; y0 = y1; }
+          if ((disp.button()&1) && (disp.button()&2)) {
+            init_pose = true; disp.set_button(); x0 = x1; y0 = y1;
+            pose = CImg<floatT>(4,3,1,1, 1,0,0,0, 0,1,0,0, 0,0,1,0);
+          }
+        } else if (clicked) { x0 = x1; y0 = y1; clicked = false; redraw = true; }
+
+        switch (key = disp.key()) {
+#if cimg_OS!=2
+        case cimg::keyCTRLRIGHT :
+#endif
+        case 0 : case cimg::keyCTRLLEFT : key = 0; break;
+        case cimg::keyD: if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) {
+            disp.set_fullscreen(false).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.set_key(key,false); key = 0;
+          } break;
+        case cimg::keyC : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) {
+            disp.set_fullscreen(false).resize(cimg_fitscreen(2*disp.width()/3,2*disp.height()/3,1),false)._is_resized = true;
+            disp.set_key(key,false); key = 0;
+          } break;
+        case cimg::keyR : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) {
+            disp.set_fullscreen(false).resize(cimg_fitscreen(_width,_height,_depth),false)._is_resized = true;
+            disp.set_key(key,false); key = 0;
+          } break;
+        case cimg::keyF : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) {
+            if (!ns_width || !ns_height ||
+                ns_width>(unsigned int)disp.screen_width() || ns_height>(unsigned int)disp.screen_height()) {
+              ns_width = disp.screen_width()*3U/4;
+              ns_height = disp.screen_height()*3U/4;
+            }
+            if (disp.is_fullscreen()) disp.resize(ns_width,ns_height,false);
+            else {
+              ns_width = (unsigned int)disp.width(); ns_height = disp.height();
+              disp.resize(disp.screen_width(),disp.screen_height(),false);
+            }
+            disp.toggle_fullscreen()._is_resized = true;
+            disp.set_key(key,false); key = 0;
+          } break;
+        case cimg::keyT : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { // Switch single/double-sided primitives.
+            if (--_double_sided==-2) _double_sided = 1;
+            if (_double_sided>=0) reverse_primitives.assign();
+            else primitives.get_reverse_object3d().move_to(reverse_primitives);
+            disp.set_key(key,false); key = 0; redraw = true;
+          } break;
+        case cimg::keyZ : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { // Enable/disable Z-buffer
+            if (zbuffer) zbuffer.assign();
+            else zbuffer.assign(visu0.width(),visu0.height(),1,1,0);
+            disp.set_key(key,false); key = 0; redraw = true;
+          } break;
+        case cimg::keyA : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { // Show/hide 3d axes.
+            ndisplay_axes = !ndisplay_axes;
+            disp.set_key(key,false); key = 0; redraw = true;
+          } break;
+        case cimg::keyF1 : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { // Set rendering mode to points.
+            nrender_motion = (nrender_static==0 && nrender_motion!=0)?0:-1; nrender_static = 0;
+            disp.set_key(key,false); key = 0; redraw = true;
+          } break;
+        case cimg::keyF2 : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { // Set rendering mode to lines.
+            nrender_motion = (nrender_static==1 && nrender_motion!=1)?1:-1; nrender_static = 1;
+            disp.set_key(key,false); key = 0; redraw = true;
+          } break;
+        case cimg::keyF3 : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { // Set rendering mode to flat.
+            nrender_motion = (nrender_static==2 && nrender_motion!=2)?2:-1; nrender_static = 2;
+            disp.set_key(key,false); key = 0; redraw = true;
+          } break;
+        case cimg::keyF4 : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { // Set rendering mode to flat-shaded.
+            nrender_motion = (nrender_static==3 && nrender_motion!=3)?3:-1; nrender_static = 3;
+            disp.set_key(key,false); key = 0; redraw = true;
+          } break;
+        case cimg::keyF5 : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { // Set rendering mode to gouraud-shaded.
+            nrender_motion = (nrender_static==4 && nrender_motion!=4)?4:-1; nrender_static = 4;
+            disp.set_key(key,false); key = 0; redraw = true;
+          } break;
+        case cimg::keyF6 : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { // Set rendering mode to phong-shaded.
+            nrender_motion = (nrender_static==5 && nrender_motion!=5)?5:-1; nrender_static = 5;
+            disp.set_key(key,false); key = 0; redraw = true;
+          } break;
+        case cimg::keyS : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { // Save snapshot
+            static unsigned int snap_number = 0;
+            char filename[32] = { 0 };
+            std::FILE *file;
+            do {
+              cimg_snprintf(filename,sizeof(filename),cimg_appname "_%.4u.bmp",snap_number++);
+              if ((file=std::fopen(filename,"r"))!=0) cimg::fclose(file);
+            } while (file);
+            (+visu).draw_text(0,0," Saving snapshot... ",foreground_color._data,background_color._data,1,13).display(disp);
+            visu.save(filename);
+            visu.draw_text(0,0," Snapshot '%s' saved. ",foreground_color._data,background_color._data,1,13,filename).display(disp);
+            disp.set_key(key,false); key = 0;
+          } break;
+        case cimg::keyG : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { // Save object as a .off file
+            static unsigned int snap_number = 0;
+            char filename[32] = { 0 };
+            std::FILE *file;
+            do {
+              cimg_snprintf(filename,sizeof(filename),cimg_appname "_%.4u.off",snap_number++);
+              if ((file=std::fopen(filename,"r"))!=0) cimg::fclose(file);
+            } while (file);
+            visu.draw_text(0,0," Saving object... ",foreground_color._data,background_color._data,1,13).display(disp);
+            vertices.save_off(filename,reverse_primitives?reverse_primitives:primitives,colors);
+            visu.draw_text(0,0," Object '%s' saved. ",foreground_color._data,background_color._data,1,13,filename).display(disp);
+            disp.set_key(key,false); key = 0;
+          } break;
+        case cimg::keyO : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { // Save object as a .cimg file
+            static unsigned int snap_number = 0;
+            char filename[32] = { 0 };
+            std::FILE *file;
+            do {
+#ifdef cimg_use_zlib
+              cimg_snprintf(filename,sizeof(filename),cimg_appname "_%.4u.cimgz",snap_number++);
+#else
+              cimg_snprintf(filename,sizeof(filename),cimg_appname "_%.4u.cimg",snap_number++);
+#endif
+              if ((file=std::fopen(filename,"r"))!=0) cimg::fclose(file);
+            } while (file);
+            visu.draw_text(0,0," Saving object... ",foreground_color._data,background_color._data,1,13).display(disp);
+            vertices.get_object3dtoCImg3d(reverse_primitives?reverse_primitives:primitives,colors,opacities).save(filename);
+            visu.draw_text(0,0," Object '%s' saved. ",foreground_color._data,background_color._data,1,13,filename).display(disp);
+            disp.set_key(key,false); key = 0;
+          } break;
+#ifdef cimg_use_board
+        case cimg::keyP : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { // Save object as a .EPS file
+            static unsigned int snap_number = 0;
+            char filename[32] = { 0 };
+            std::FILE *file;
+            do {
+              cimg_snprintf(filename,sizeof(filename),cimg_appname "_%.4u.eps",snap_number++);
+              if ((file=std::fopen(filename,"r"))!=0) cimg::fclose(file);
+            } while (file);
+            visu.draw_text(0,0," Saving EPS snapshot... ",foreground_color._data,background_color._data,1,13).display(disp);
+            LibBoard::Board board;
+            (+visu)._draw_object3d(&board,zbuffer.fill(0),
+                                   Xoff + visu._width/2.0f,Yoff + visu._height/2.0f,Zoff,
+                                   rotated_vertices,reverse_primitives?reverse_primitives:primitives,
+                                   colors,opacities,clicked?nrender_motion:nrender_static,
+                                   _double_sided==1,focale,
+                                   visu.width()/2.0f+light_x,visu.height()/2.0f+light_y,light_z,specular_light,specular_shine,
+                                   sprite_scale);
+            board.saveEPS(filename);
+            visu.draw_text(0,0," Object '%s' saved. ",foreground_color._data,background_color._data,1,13,filename).display(disp);
+            disp.set_key(key,false); key = 0;
+          } break;
+        case cimg::keyV : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { // Save object as a .SVG file
+            static unsigned int snap_number = 0;
+            char filename[32] = { 0 };
+            std::FILE *file;
+            do {
+              cimg_snprintf(filename,sizeof(filename),cimg_appname "_%.4u.svg",snap_number++);
+              if ((file=std::fopen(filename,"r"))!=0) cimg::fclose(file);
+            } while (file);
+            visu.draw_text(0,0," Saving SVG snapshot... ",foreground_color._data,background_color._data,1,13).display(disp);
+            LibBoard::Board board;
+            (+visu)._draw_object3d(&board,zbuffer.fill(0),
+                                   Xoff + visu._width/2.0f,Yoff + visu._height/2.0f,Zoff,
+                                   rotated_vertices,reverse_primitives?reverse_primitives:primitives,
+                                   colors,opacities,clicked?nrender_motion:nrender_static,
+                                   _double_sided==1,focale,
+                                   visu.width()/2.0f+light_x,visu.height()/2.0f+light_y,light_z,specular_light,specular_shine,
+                                   sprite_scale);
+            board.saveSVG(filename);
+            visu.draw_text(0,0," Object '%s' saved. ",foreground_color._data,background_color._data,1,13,filename).display(disp);
+            disp.set_key(key,false); 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) {
+        std::memcpy(pose_matrix,pose._data,12*sizeof(float));
+        pose_matrix[12] = Xoff; pose_matrix[13] = Yoff; pose_matrix[14] = Zoff; pose_matrix[15] = sprite_scale;
+      }
+      disp.set_button().set_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_instance
+                                    "display_graph() : Empty instance.",
+                                    cimg_instance);
+      if (!disp) disp.assign(cimg_fitscreen(640,480,1),0,0).set_title("CImg<%s>",pixel_type());
+      const unsigned int siz = _width*_height*_depth, siz1 = cimg::max(1U,siz-1), old_normalization = disp.normalization();
+      disp.show().flush()._normalization = 0;
+
+      double y0 = ymin, y1 = ymax, nxmin = xmin, nxmax = xmax;
+      if (nxmin==nxmax) { nxmin = 0; nxmax = siz1; }
+      int x0 = 0, x1 = width()*height()*depth() - 1, key = 0;
+
+      for (bool reset_view = true, resize_disp = false; !key && !disp.is_closed(); ) {
+        if (reset_view) { x0 = 0; x1 = width()*height()*depth()-1; y0 = ymin; y1 = ymax; reset_view = false; }
+        CImg<T> zoom(x1-x0+1,1,1,spectrum());
+        cimg_forC(*this,c) zoom.get_shared_channel(c) = CImg<T>(data(x0,0,0,c),x1-x0+1,1,1,1,true);
+
+        if (y0==y1) { y0 = zoom.min_max(y1); const double dy = y1 - y0; y0-=dy/20; y1+=dy/20; }
+        if (y0==y1) { --y0; ++y1; }
+        const CImg<intT> selection = zoom.get_select_graph(disp,plot_type,vertex_type,
+                                                           labelx,
+                                                           nxmin + x0*(nxmax-nxmin)/siz1,
+                                                           nxmin + x1*(nxmax-nxmin)/siz1,
+                                                           labely,y0,y1);
+        const int mouse_x = disp.mouse_x(), mouse_y = disp.mouse_y();
+        if (selection[0]>=0) {
+          if (selection[2]<0) reset_view = true;
+          else {
+            x1 = x0 + selection[2]; x0+=selection[0];
+            if (selection[1]>=0 && selection[3]>=0) {
+              y0 = y1 - selection[3]*(y1-y0)/(disp.height()-32);
+              y1-=selection[1]*(y1-y0)/(disp.height()-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 : reset_view = resize_disp = true; key = 0; disp.set_key(); break;
+          case cimg::keyPADADD : go_in = true; go_out = false; key = 0; disp.set_key(); break;
+          case cimg::keyPADSUB : go_out = true; go_in = false; key = 0; disp.set_key(); break;
+          case cimg::keyARROWLEFT : case cimg::keyPAD4 : go_left = true; go_right = false; key = 0; disp.set_key(); break;
+          case cimg::keyARROWRIGHT : case cimg::keyPAD6 : go_right = true; go_left = false; key = 0; disp.set_key(); break;
+          case cimg::keyARROWUP : case cimg::keyPAD8 : go_up = true; go_down = false; key = 0; disp.set_key(); break;
+          case cimg::keyARROWDOWN : case cimg::keyPAD2 : go_down = true; go_up = false; key = 0; disp.set_key(); break;
+          case cimg::keyPAD7 : go_left = true; go_up = true; key = 0; disp.set_key(); break;
+          case cimg::keyPAD9 : go_right = true; go_up = true; key = 0; disp.set_key(); break;
+          case cimg::keyPAD1 : go_left = true; go_down = true; key = 0; disp.set_key(); break;
+          case cimg::keyPAD3 : go_right = true; go_down = true; key = 0; disp.set_key(); break;
+          }
+          if (disp.wheel()) {
+            if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) go_out = !(go_in = disp.wheel()>0);
+            else if (disp.is_keySHIFTLEFT() || disp.is_keySHIFTRIGHT()) go_left = !(go_right = disp.wheel()>0);
+            else go_up = !(go_down = disp.wheel()<0);
+            key = 0;
+          }
+
+          if (go_in) {
+            const int
+              xsiz = x1 - x0,
+              mx = (mouse_x-16)*xsiz/(disp.width()-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() || disp.is_keyCTRLRIGHT()) {
+                const double
+                  ysiz = y1 - y0,
+                  my = (mouse_y-16)*ysiz/(disp.height()-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) {
+            if (x0>0 || x1<(int)siz1) {
+              const int deltax = (x1-x0)/8, ndeltax = deltax?deltax:(siz>1?1:0);
+              const double ndeltay = (y1-y0)/8;
+              x0-=ndeltax; x1+=ndeltax;
+              y0-=ndeltay; y1+=ndeltay;
+              if (x0<0) { x1-=x0; x0 = 0; if (x1>=(int)siz) x1 = (int)siz1; }
+              if (x1>=(int)siz) { x0-=(x1-siz1); x1 = (int)siz1; if (x0<0) x0 = 0; }
+            }
+          }
+          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+=(siz1-x1); x1 = siz1; }
+            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 = old_normalization;
+      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_instance
+                                    "display_graph() : Empty instance.",
+                                    cimg_instance);
+      CImgDisplay disp;
+      return display_graph(disp.set_title("%s",title),plot_type,vertex_type,labelx,xmin,xmax,labely,ymin,ymax);
+    }
+
+    //! 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 (!filename)
+        throw CImgArgumentException(_cimg_instance
+                                    "save() : Specified filename is (null).",
+                                    cimg_instance);
+      // Do not test for empty instances, since .cimg format is able to manage empty instances.
+      const char *const ext = cimg::split_filename(filename);
+      char nfilename[1024] = { 0 };
+      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);
+      else if (!cimg::strcasecmp(ext,"dlm") ||
+               !cimg::strcasecmp(ext,"txt")) return save_dlm(fn);
+      else if (!cimg::strcasecmp(ext,"cpp") ||
+               !cimg::strcasecmp(ext,"hpp") ||
+               !cimg::strcasecmp(ext,"h") ||
+               !cimg::strcasecmp(ext,"c")) return save_cpp(fn);
+
+      // 2d binary formats
+      else if (!cimg::strcasecmp(ext,"bmp")) return save_bmp(fn);
+      else 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);
+      else if (!cimg::strcasecmp(ext,"rgb")) return save_rgb(fn);
+      else if (!cimg::strcasecmp(ext,"rgba")) return save_rgba(fn);
+      else if (!cimg::strcasecmp(ext,"png")) return save_png(fn);
+      else if (!cimg::strcasecmp(ext,"pgm") ||
+               !cimg::strcasecmp(ext,"ppm") ||
+               !cimg::strcasecmp(ext,"pnm")) return save_pnm(fn);
+      else if (!cimg::strcasecmp(ext,"pnk")) return save_pnk(fn);
+      else if (!cimg::strcasecmp(ext,"pfm")) return save_pfm(fn);
+      else if (!cimg::strcasecmp(ext,"exr")) return save_exr(fn);
+      else if (!cimg::strcasecmp(ext,"tif") ||
+               !cimg::strcasecmp(ext,"tiff")) return save_tiff(fn);
+
+      // 3d binary formats
+      else if (!cimg::strcasecmp(ext,"cimgz")) return save_cimg(fn,true);
+      else if (!cimg::strcasecmp(ext,"cimg") || !*ext) return save_cimg(fn,false);
+      else if (!cimg::strcasecmp(ext,"dcm")) return save_medcon_external(fn);
+      else if (!cimg::strcasecmp(ext,"hdr") ||
+               !cimg::strcasecmp(ext,"nii")) return save_analyze(fn);
+      else if (!cimg::strcasecmp(ext,"inr")) return save_inr(fn);
+      else if (!cimg::strcasecmp(ext,"mnc")) return save_minc2(fn);
+      else if (!cimg::strcasecmp(ext,"pan")) return save_pandore(fn);
+      else if (!cimg::strcasecmp(ext,"raw")) return save_raw(fn);
+
+      // Archive files
+      else if (!cimg::strcasecmp(ext,"gz")) return save_gzip_external(fn);
+
+      // Image sequences
+      else if (!cimg::strcasecmp(ext,"yuv")) return save_yuv(fn,true);
+      else if (!cimg::strcasecmp(ext,"avi") ||
+               !cimg::strcasecmp(ext,"mov") ||
+               !cimg::strcasecmp(ext,"asf") ||
+               !cimg::strcasecmp(ext,"divx") ||
+               !cimg::strcasecmp(ext,"flv") ||
+               !cimg::strcasecmp(ext,"mpg") ||
+               !cimg::strcasecmp(ext,"m1v") ||
+               !cimg::strcasecmp(ext,"m2v") ||
+               !cimg::strcasecmp(ext,"m4v") ||
+               !cimg::strcasecmp(ext,"mjp") ||
+               !cimg::strcasecmp(ext,"mkv") ||
+               !cimg::strcasecmp(ext,"mpe") ||
+               !cimg::strcasecmp(ext,"movie") ||
+               !cimg::strcasecmp(ext,"ogm") ||
+               !cimg::strcasecmp(ext,"ogg") ||
+               !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(std::FILE *const file, const char *const filename) const {
+      if (!file && !filename)
+        throw CImgArgumentException(_cimg_instance
+                                    "save_ascii() : Specified filename is (null).",
+                                    cimg_instance);
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "save_ascii() : Empty instance, for file '%s'.",
+                                    cimg_instance,
+                                    filename?filename:"(FILE*)");
+
+      std::FILE *const nfile = file?file:cimg::fopen(filename,"w");
+      std::fprintf(nfile,"%u %u %u %u\n",_width,_height,_depth,_spectrum);
+      const T* ptrs = _data;
+      cimg_forYZC(*this,y,z,c) {
+        cimg_forX(*this,x) std::fprintf(nfile,"%g ",(double)*(ptrs++));
+        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(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(std::FILE *const file, const char *const filename) const {
+      if (!file && !filename)
+        throw CImgArgumentException(_cimg_instance
+                                    "save_cpp() : Specified filename is (null).",
+                                    cimg_instance);
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "save_cpp() : Empty instance, for file '%s'.",
+                                    cimg_instance,
+                                    filename?filename:"(FILE*)");
+
+      std::FILE *const nfile = file?file:cimg::fopen(filename,"w");
+      char varname[1024] = { 0 };
+      if (filename) std::sscanf(cimg::basename(filename),"%1023[a-zA-Z0-9_]",varname);
+      if (!*varname) cimg_snprintf(varname,sizeof(varname),"unnamed");
+      std::fprintf(nfile,
+                   "/* Define image '%s' of size %ux%ux%ux%u and type '%s' */\n"
+                   "%s data_%s[] = { \n  ",
+                   varname,_width,_height,_depth,_spectrum,pixel_type(),pixel_type(),varname);
+      for (unsigned int off = 0, siz = size()-1; off<=siz; ++off) {
+        std::fprintf(nfile,cimg::type<T>::format(),cimg::type<T>::format((*this)[off]));
+        if (off==siz) std::fprintf(nfile," };\n");
+        else if (!((off+1)%16)) std::fprintf(nfile,",\n  ");
+        else 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(std::FILE *const file) const {
+      return _save_cpp(file,0);
+    }
+
+    // Save the image as a DLM file (internal).
+    const CImg<T>& _save_dlm(std::FILE *const file, const char *const filename) const {
+      if (!file && !filename)
+        throw CImgArgumentException(_cimg_instance
+                                    "save_dlm() : Specified filename is (null).",
+                                    cimg_instance);
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "save_dlm() : Empty instance, for file '%s'.",
+                                    cimg_instance,
+                                    filename?filename:"(FILE*)");
+      if (_depth>1)
+        cimg::warn(_cimg_instance
+                   "save_dlm() : Instance is volumetric, values along Z will be unrolled in file '%s'.",
+                   cimg_instance,
+                   filename?filename:"(FILE*)");
+
+      if (_spectrum>1)
+        cimg::warn(_cimg_instance
+                   "save_dlm() : Instance is multispectral, values along C will be unrolled in file '%s'.",
+                   cimg_instance,
+                   filename?filename:"(FILE*)");
+
+      std::FILE *const nfile = file?file:cimg::fopen(filename,"w");
+      const T* ptrs = _data;
+      cimg_forYZC(*this,y,z,c) {
+        cimg_forX(*this,x) std::fprintf(nfile,"%g%s",(double)*(ptrs++),(x==width()-1)?"":",");
+        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(std::FILE *const file) const {
+      return _save_dlm(file,0);
+    }
+
+   // Save the image as a BMP file (internal).
+    const CImg<T>& _save_bmp(std::FILE *const file, const char *const filename) const {
+      if (!file && !filename)
+        throw CImgArgumentException(_cimg_instance
+                                    "save_bmp() : Specified filename is (null).",
+                                    cimg_instance);
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "save_bmp() : Empty instance, for file '%s'.",
+                                    cimg_instance,
+                                    filename?filename:"(FILE*)");
+      if (_depth>1)
+        cimg::warn(_cimg_instance
+                   "save_bmp() : Instance is volumetric, only the first slice will be saved in file '%s'.",
+                   cimg_instance,
+                   filename?filename:"(FILE*)");
+
+      if (_spectrum>3)
+        cimg::warn(_cimg_instance
+                   "save_bmp() : Instance is multispectral, only the three first channels will be saved in file '%s'.",
+                   cimg_instance,
+                   filename?filename:"(FILE*)");
+
+      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)*height(),
+        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
+        *ptr_r = data(0,_height-1,0,0),
+        *ptr_g = (_spectrum>=2)?data(0,_height-1,0,1):0,
+        *ptr_b = (_spectrum>=3)?data(0,_height-1,0,2):0;
+
+      switch (_spectrum) {
+      case 1 : {
+        cimg_forY(*this,y) {
+          cimg_forX(*this,x) {
+            const unsigned char val = (unsigned char)*(ptr_r++);
+            std::fputc(val,nfile); std::fputc(val,nfile); std::fputc(val,nfile);
+          }
+          cimg::fwrite(align_buf,align,nfile);
+          ptr_r-=2*_width;
+        }
+      } break;
+      case 2 : {
+        cimg_forY(*this,y) {
+          cimg_forX(*this,x) {
+            std::fputc(0,nfile);
+            std::fputc((unsigned char)(*(ptr_g++)),nfile);
+            std::fputc((unsigned char)(*(ptr_r++)),nfile);
+          }
+          cimg::fwrite(align_buf,align,nfile);
+          ptr_r-=2*_width; ptr_g-=2*_width;
+        }
+      } break;
+      default : {
+        cimg_forY(*this,y) {
+          cimg_forX(*this,x) {
+            std::fputc((unsigned char)(*(ptr_b++)),nfile);
+            std::fputc((unsigned char)(*(ptr_g++)),nfile);
+            std::fputc((unsigned char)(*(ptr_r++)),nfile);
+          }
+          cimg::fwrite(align_buf,align,nfile);
+          ptr_r-=2*_width; ptr_g-=2*_width; ptr_b-=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(std::FILE *const file) const {
+      return _save_bmp(file,0);
+    }
+
+    // Save a file in JPEG format (internal).
+    const CImg<T>& _save_jpeg(std::FILE *const file, const char *const filename, const unsigned int quality) const {
+      if (!file && !filename)
+        throw CImgArgumentException(_cimg_instance
+                                    "save_jpeg() : Specified filename is (null).",
+                                    cimg_instance);
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "save_jpeg() : Empty instance, for file '%s'.",
+                                    cimg_instance,
+                                    filename?filename:"(FILE*)");
+      if (_depth>1)
+        cimg::warn(_cimg_instance
+                   "save_jpeg() : Instance is volumetric, only the first slice will be saved in file '%s'.",
+                   cimg_instance,
+                   filename?filename:"(FILE*)");
+
+#ifndef cimg_use_jpeg
+      if (!file) return save_other(filename,quality);
+      else throw CImgIOException(_cimg_instance
+                                 "save_jpeg() : Unable to save data in '(*FILE)' unless libjpeg is enabled.",
+                                 cimg_instance);
+#else
+      unsigned int dimbuf = 0;
+      J_COLOR_SPACE colortype = JCS_RGB;
+
+      switch(_spectrum) {
+      case 1 : dimbuf = 1; colortype = JCS_GRAYSCALE; break;
+      case 2 : dimbuf = 3; colortype = JCS_RGB; break;
+      case 3 : dimbuf = 3; colortype = JCS_RGB; break;
+      default: dimbuf = 4; colortype = JCS_CMYK; break;
+      }
+
+      // Call libjpeg functions
+      struct jpeg_compress_struct cinfo;
+      struct jpeg_error_mgr jerr;
+      cinfo.err = jpeg_std_error(&jerr);
+      jpeg_create_compress(&cinfo);
+      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);
+
+      JSAMPROW row_pointer[1];
+      CImg<ucharT> buffer(_width*dimbuf);
+
+      while (cinfo.next_scanline < cinfo.image_height) {
+        unsigned char *ptrd = buffer._data;
+
+        // Fill pixel buffer
+        switch (_spectrum) {
+        case 1 : { // Greyscale images
+          const T *ptr_g = data(0, cinfo.next_scanline);
+          for(unsigned int b = 0; b < cinfo.image_width; b++)
+            *(ptrd++) = (unsigned char)*(ptr_g++);
+        } break;
+        case 2 : { // RG images
+          const T *ptr_r = data(0,cinfo.next_scanline,0,0),
+            *ptr_g = data(0,cinfo.next_scanline,0,1);
+          for(unsigned int b = 0; b < cinfo.image_width; ++b) {
+            *(ptrd++) = (unsigned char)*(ptr_r++);
+            *(ptrd++) = (unsigned char)*(ptr_g++);
+            *(ptrd++) = 0;
+          }
+        } break;
+        case 3 : { // RGB images
+          const T *ptr_r = data(0,cinfo.next_scanline,0,0),
+            *ptr_g = data(0,cinfo.next_scanline,0,1),
+            *ptr_b = data(0,cinfo.next_scanline,0,2);
+          for(unsigned int b = 0; b < cinfo.image_width; ++b) {
+            *(ptrd++) = (unsigned char)*(ptr_r++);
+            *(ptrd++) = (unsigned char)*(ptr_g++);
+            *(ptrd++) = (unsigned char)*(ptr_b++);
+          }
+        } break;
+        default : { // CMYK images
+          const T *ptr_r = data(0,cinfo.next_scanline,0,0),
+            *ptr_g = data(0,cinfo.next_scanline,0,1),
+            *ptr_b = data(0,cinfo.next_scanline,0,2),
+            *ptr_a = data(0,cinfo.next_scanline,0,3);
+          for(unsigned int b = 0; b < cinfo.image_width; ++b) {
+            *(ptrd++) = (unsigned char)*(ptr_r++);
+            *(ptrd++) = (unsigned char)*(ptr_g++);
+            *(ptrd++) = (unsigned char)*(ptr_b++);
+            *(ptrd++) = (unsigned char)*(ptr_a++);
+          }
+        }
+        }
+        *row_pointer = buffer._data;
+        jpeg_write_scanlines(&cinfo,row_pointer,1);
+      }
+      jpeg_finish_compress(&cinfo);
+      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(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 unsigned int bytes_per_pixel=0) const {
+      if (!filename)
+        throw CImgArgumentException(_cimg_instance
+                                    "save_magick() : Specified filename is (null).",
+                                    cimg_instance);
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "save_magick() : Empty instance, for file '%s'.",
+                                    cimg_instance,
+                                    filename);
+#ifdef cimg_use_magick
+      double stmin, stmax = (double)max_min(stmin);
+      if (_depth>1)
+        cimg::warn(_cimg_instance
+                   "save_magick() : Instance is volumetric, only the first slice will be saved in file '%s'.",
+                   cimg_instance,
+                   filename);
+
+      if (_spectrum>3)
+        cimg::warn(_cimg_instance
+                   "save_magick() : Instance is multispectral, only the three first channels will be saved in file '%s'.",
+                   cimg_instance,
+                   filename);
+
+      if (stmin<0 || (bytes_per_pixel==1 && stmax>=256) || stmax>=65536)
+        cimg::warn(_cimg_instance
+                   "save_magick() : Instance has pixel values in [%g,%g], probable type overflow in file '%s'.",
+                   cimg_instance,
+                   filename,stmin,stmax);
+
+      Magick::Image image(Magick::Geometry(_width,_height),"black");
+      image.type(Magick::TrueColorType);
+      image.depth(bytes_per_pixel?(8*bytes_per_pixel):(stmax>=256?16:8));
+      const T
+        *ptr_r = data(0,0,0,0),
+        *ptr_g = _spectrum>1?data(0,0,0,1):0,
+        *ptr_b = _spectrum>2?data(0,0,0,2):0;
+      Magick::PixelPacket *pixels = image.getPixels(0,0,_width,_height);
+      switch (_spectrum) {
+      case 1 : // Scalar images
+        for (unsigned int off = _width*_height; off; --off) {
+          pixels->red = pixels->green = pixels->blue = (Magick::Quantum)*(ptr_r++);
+          ++pixels;
+        }
+        break;
+      case 2 : // RG images
+        for (unsigned int off = _width*_height; off; --off) {
+          pixels->red = (Magick::Quantum)*(ptr_r++);
+          pixels->green = (Magick::Quantum)*(ptr_g++);
+          pixels->blue = 0; ++pixels;
+        }
+        break;
+      default : // RGB images
+        for (unsigned int off = _width*_height; off; --off) {
+          pixels->red = (Magick::Quantum)*(ptr_r++);
+          pixels->green = (Magick::Quantum)*(ptr_g++);
+          pixels->blue = (Magick::Quantum)*(ptr_b++);
+          ++pixels;
+        }
+      }
+      image.syncPixels();
+      image.write(filename);
+#else
+      cimg::unused(bytes_per_pixel);
+      throw CImgIOException(_cimg_instance
+                            "save_magick() : Unable to save file '%s' unless libMagick++ is enabled.",
+                            cimg_instance,
+                            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(std::FILE *const file, const char *const filename, const unsigned int bytes_per_pixel=0) const {
+      if (!file && !filename)
+        throw CImgArgumentException(_cimg_instance
+                                    "save_png() : Specified filename is (null).",
+                                    cimg_instance);
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "save_png() : Empty image, for file '%s'.",
+                                    cimg_instance,
+                                    filename?filename:"(FILE*)");
+#ifndef cimg_use_png
+      cimg::unused(bytes_per_pixel);
+      if (!file) return save_other(filename);
+      else throw CImgIOException(_cimg_instance
+                                 "save_png() : Unable to save data in '(*FILE)' unless libpng is enabled.",
+                                 cimg_instance);
+#else
+      const char *volatile nfilename = filename; // two 'volatile' here to remove a g++ warning due to 'setjmp'.
+      std::FILE *volatile nfile = file?file:cimg::fopen(nfilename,"wb");
+
+      double stmin, stmax = (double)max_min(stmin);
+      if (_depth>1)
+        cimg::warn(_cimg_instance
+                   "save_png() : Instance is volumetric, only the first slice will be saved in file '%s'.",
+                   cimg_instance,
+                   filename);
+
+      if (_spectrum>4)
+        cimg::warn(_cimg_instance
+                   "save_png() : Instance is multispectral, only the three first channels will be saved in file '%s'.",
+                   cimg_instance,
+                   filename);
+
+      if (stmin<0 || (bytes_per_pixel==1 && stmax>=256) || stmax>=65536)
+        cimg::warn(_cimg_instance
+                   "save_png() : Instance has pixel values in [%g,%g], probable type overflow in file '%s'.",
+                   cimg_instance,
+                   filename,stmin,stmax);
+
+      // Setup PNG structures for write
+      png_voidp user_error_ptr = 0;
+      png_error_ptr user_error_fn = 0, user_warning_fn = 0;
+      png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING,user_error_ptr, user_error_fn, user_warning_fn);
+      if(!png_ptr){
+        if (!file) cimg::fclose(nfile);
+        throw CImgIOException(_cimg_instance
+                              "save_png() : Failed to initialize 'png_ptr' structure when saving file '%s'.",
+                              cimg_instance,
+                              nfilename?nfilename:"(FILE*)");
+      }
+      png_infop info_ptr = png_create_info_struct(png_ptr);
+      if (!info_ptr) {
+        png_destroy_write_struct(&png_ptr,(png_infopp)0);
+        if (!file) cimg::fclose(nfile);
+        throw CImgIOException(_cimg_instance
+                              "save_png() : Failed to initialize 'info_ptr' structure when saving file '%s'.",
+                              cimg_instance,
+                              nfilename?nfilename:"(FILE*)");
+      }
+      if (setjmp(png_jmpbuf(png_ptr))) {
+        png_destroy_write_struct(&png_ptr, &info_ptr);
+        if (!file) cimg::fclose(nfile);
+        throw CImgIOException(_cimg_instance
+                              "save_png() : Encountered unknown fatal error in libpng when saving file '%s'.",
+                              cimg_instance,
+                              nfilename?nfilename:"(FILE*)");
+      }
+      png_init_io(png_ptr, nfile);
+      const int bit_depth = bytes_per_pixel?(bytes_per_pixel*8):(stmax>=256?16:8);
+      int color_type;
+      switch (spectrum()) {
+      case 1 : color_type = PNG_COLOR_TYPE_GRAY; break;
+      case 2 : color_type = PNG_COLOR_TYPE_GRAY_ALPHA; break;
+      case 3 : color_type = PNG_COLOR_TYPE_RGB; break;
+      default : color_type = PNG_COLOR_TYPE_RGB_ALPHA;
+      }
+      const int interlace_type = PNG_INTERLACE_NONE;
+      const int compression_type = PNG_COMPRESSION_TYPE_DEFAULT;
+      const int filter_method = PNG_FILTER_TYPE_DEFAULT;
+      png_set_IHDR(png_ptr,info_ptr,_width,_height,bit_depth,color_type,interlace_type,compression_type,filter_method);
+      png_write_info(png_ptr,info_ptr);
+      const int byte_depth = bit_depth>>3;
+      const int numChan = spectrum()>4?4:spectrum();
+      const int pixel_bit_depth_flag = numChan * (bit_depth-1);
+
+      // Allocate Memory for Image Save and Fill pixel data
+      png_bytep *const imgData = new png_byte*[_height];
+      for (unsigned int row = 0; row<_height; ++row) imgData[row] = new png_byte[byte_depth*numChan*_width];
+      const T *pC0 = data(0,0,0,0);
+      switch (pixel_bit_depth_flag) {
+      case 7 :  { // Gray 8-bit
+        cimg_forY(*this,y) {
+          unsigned char *ptrd = imgData[y];
+          cimg_forX(*this,x) *(ptrd++) = (unsigned char)*(pC0++);
+        }
+      } break;
+      case 14 : { // Gray w/ Alpha 8-bit
+        const T *pC1 = data(0,0,0,1);
+        cimg_forY(*this,y) {
+          unsigned char *ptrd = imgData[y];
+          cimg_forX(*this,x) {
+            *(ptrd++) = (unsigned char)*(pC0++);
+            *(ptrd++) = (unsigned char)*(pC1++);
+          }
+        }
+      } break;
+      case 21 :  { // RGB 8-bit
+        const T *pC1 = data(0,0,0,1), *pC2 = data(0,0,0,2);
+        cimg_forY(*this,y) {
+          unsigned char *ptrd = imgData[y];
+          cimg_forX(*this,x) {
+            *(ptrd++) = (unsigned char)*(pC0++);
+            *(ptrd++) = (unsigned char)*(pC1++);
+            *(ptrd++) = (unsigned char)*(pC2++);
+          }
+        }
+      } break;
+      case 28 : { // RGB x/ Alpha 8-bit
+        const T *pC1 = data(0,0,0,1), *pC2 = data(0,0,0,2), *pC3 = data(0,0,0,3);
+        cimg_forY(*this,y){
+          unsigned char *ptrd = imgData[y];
+          cimg_forX(*this,x){
+            *(ptrd++) = (unsigned char)*(pC0++);
+            *(ptrd++) = (unsigned char)*(pC1++);
+            *(ptrd++) = (unsigned char)*(pC2++);
+            *(ptrd++) = (unsigned char)*(pC3++);
+          }
+        }
+      } break;
+      case 15 : { // Gray 16-bit
+        cimg_forY(*this,y){
+          unsigned short *ptrd = (unsigned short*)(imgData[y]);
+          cimg_forX(*this,x) *(ptrd++) = (unsigned short)*(pC0++);
+          if (!cimg::endianness()) cimg::invert_endianness((unsigned short*)imgData[y],_width);
+        }
+      } break;
+      case 30 : { // Gray w/ Alpha 16-bit
+        const T *pC1 = data(0,0,0,1);
+        cimg_forY(*this,y){
+          unsigned short *ptrd = (unsigned short*)(imgData[y]);
+          cimg_forX(*this,x) {
+            *(ptrd++) = (unsigned short)*(pC0++);
+            *(ptrd++) = (unsigned short)*(pC1++);
+          }
+          if (!cimg::endianness()) cimg::invert_endianness((unsigned short*)imgData[y],2*_width);
+        }
+      } break;
+      case 45 : { // RGB 16-bit
+        const T *pC1 = data(0,0,0,1), *pC2 = data(0,0,0,2);
+        cimg_forY(*this,y) {
+          unsigned short *ptrd = (unsigned short*)(imgData[y]);
+          cimg_forX(*this,x) {
+            *(ptrd++) = (unsigned short)*(pC0++);
+            *(ptrd++) = (unsigned short)*(pC1++);
+            *(ptrd++) = (unsigned short)*(pC2++);
+          }
+          if (!cimg::endianness()) cimg::invert_endianness((unsigned short*)imgData[y],3*_width);
+        }
+      } break;
+      case 60 : { // RGB w/ Alpha 16-bit
+        const T *pC1 = data(0,0,0,1), *pC2 = data(0,0,0,2), *pC3 = data(0,0,0,3);
+        cimg_forY(*this,y) {
+          unsigned short *ptrd = (unsigned short*)(imgData[y]);
+          cimg_forX(*this,x) {
+            *(ptrd++) = (unsigned short)*(pC0++);
+            *(ptrd++) = (unsigned short)*(pC1++);
+            *(ptrd++) = (unsigned short)*(pC2++);
+            *(ptrd++) = (unsigned short)*(pC3++);
+          }
+          if (!cimg::endianness()) cimg::invert_endianness((unsigned short*)imgData[y],4*_width);
+        }
+      } break;
+      default :
+        if (!file) cimg::fclose(nfile);
+        throw CImgIOException(_cimg_instance
+                              "save_png() : Encountered unknown fatal error in libpng when saving file '%s'.",
+                              cimg_instance,
+                              nfilename?nfilename:"(FILE*)");
+      }
+      png_write_image(png_ptr,imgData);
+      png_write_end(png_ptr,info_ptr);
+      png_destroy_write_struct(&png_ptr, &info_ptr);
+
+      // Deallocate Image Write Memory
+      cimg_forY(*this,n) delete[] imgData[n];
+      delete[] imgData;
+      if (!file) cimg::fclose(nfile);
+      return *this;
+#endif
+    }
+
+    //! Save a file in PNG format
+    const CImg<T>& save_png(const char *const filename, const unsigned int bytes_per_pixel=0) const {
+      return _save_png(0,filename,bytes_per_pixel);
+    }
+
+    //! Save a file in PNG format
+    const CImg<T>& save_png(std::FILE *const file, const unsigned int bytes_per_pixel=0) const {
+      return _save_png(file,0,bytes_per_pixel);
+    }
+
+    //! Save the image as a PNM file.
+    const CImg<T>& save_pnm(const char *const filename, const unsigned int bytes_per_pixel=0) const {
+      return _save_pnm(0,filename,bytes_per_pixel);
+    }
+
+    //! Save the image as a PNM file.
+    const CImg<T>& save_pnm(std::FILE *const file, const unsigned int bytes_per_pixel=0) const {
+      return _save_pnm(file,0,bytes_per_pixel);
+    }
+
+    // Save the image as a PNM file (internal function).
+    const CImg<T>& _save_pnm(std::FILE *const file, const char *const filename, const unsigned int bytes_per_pixel=0) const {
+      if (!file && !filename)
+        throw CImgArgumentException(_cimg_instance
+                                    "save_pnm() : Specified filename is (null).",
+                                    cimg_instance);
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "save_pnm() : Empty instance, for file '%s'.",
+                                    cimg_instance,
+                                    filename?filename:"(FILE*)");
+
+      double stmin, stmax = (double)max_min(stmin);
+      if (_depth>1)
+        cimg::warn(_cimg_instance
+                   "save_pnm() : Instance is volumetric, only the first slice will be saved in file '%s'.",
+                   cimg_instance,
+                   filename?filename:"(FILE*)");
+
+      if (_spectrum>3)
+        cimg::warn(_cimg_instance
+                   "save_pnm() : Instance is multispectral, only the three first channels will be saved in file '%s'.",
+                   cimg_instance,
+                   filename?filename:"(FILE*)");
+
+      if (stmin<0 || (bytes_per_pixel==1 && stmax>=256) || stmax>=65536)
+        cimg::warn(_cimg_instance
+                   "save_pnm() : Instance has pixel values in [%g,%g], probable type overflow in file '%s'.",
+                   cimg_instance,
+                   stmin,stmax,filename?filename:"(FILE*)");
+
+      std::FILE *const nfile = file?file:cimg::fopen(filename,"wb");
+      const T
+        *ptr_r = data(0,0,0,0),
+        *ptr_g = (_spectrum>=2)?data(0,0,0,1):0,
+        *ptr_b = (_spectrum>=3)?data(0,0,0,2):0;
+      const unsigned int buf_size = cimg::min(1024*1024U,_width*_height*(_spectrum==1?1:3));
+
+      std::fprintf(nfile,"P%c\n%u %u\n%u\n",
+                   (_spectrum==1?'5':'6'),_width,_height,stmax<256?255:(stmax<4096?4095:65535));
+
+      switch (_spectrum) {
+      case 1 : { // Scalar image
+        if (bytes_per_pixel==1 || (!bytes_per_pixel && stmax<256)) { // Binary PGM 8 bits
+          CImg<ucharT> buf(buf_size);
+          for (long to_write = _width*_height; to_write>0; ) {
+            const unsigned long N = cimg::min((unsigned long)to_write,buf_size);
+            unsigned char *ptrd = buf._data;
+            for (long i = (long)N; i>0; --i) *(ptrd++) = (unsigned char)*(ptr_r++);
+            cimg::fwrite(buf._data,N,nfile);
+            to_write-=N;
+          }
+        } else { // Binary PGM 16 bits
+          CImg<ushortT> buf(buf_size);
+          for (long to_write = _width*_height; to_write>0; ) {
+            const unsigned long N = cimg::min((unsigned long)to_write,buf_size);
+            unsigned short *ptrd = buf._data;
+            for (long i = (long)N; i>0; --i) *(ptrd++) = (unsigned short)*(ptr_r++);
+            if (!cimg::endianness()) cimg::invert_endianness(buf._data,buf_size);
+            cimg::fwrite(buf._data,N,nfile);
+            to_write-=N;
+          }
+        }
+      } break;
+      case 2 : { // RG image
+        if (bytes_per_pixel==1 || (!bytes_per_pixel && stmax<256)) { // Binary PPM 8 bits
+          CImg<ucharT> buf(buf_size);
+          for (long to_write = _width*_height; to_write>0; ) {
+            const unsigned long N = cimg::min((unsigned long)to_write,buf_size/3);
+            unsigned char *ptrd = buf._data;
+            for (long i = (long)N; i>0; --i) {
+              *(ptrd++) = (unsigned char)*(ptr_r++);
+              *(ptrd++) = (unsigned char)*(ptr_g++);
+              *(ptrd++) = 0;
+            }
+            cimg::fwrite(buf._data,3*N,nfile);
+            to_write-=N;
+          }
+        } else {             // Binary PPM 16 bits
+          CImg<ushortT> buf(buf_size);
+          for (long to_write = _width*_height; to_write>0; ) {
+            const unsigned long N = cimg::min((unsigned long)to_write,buf_size/3);
+            unsigned short *ptrd = buf._data;
+            for (long i = (long)N; i>0; --i) {
+              *(ptrd++) = (unsigned short)*(ptr_r++);
+              *(ptrd++) = (unsigned short)*(ptr_g++);
+              *(ptrd++) = 0;
+            }
+            if (!cimg::endianness()) cimg::invert_endianness(buf._data,buf_size);
+            cimg::fwrite(buf._data,3*N,nfile);
+            to_write-=N;
+          }
+        }
+      } break;
+      default : { // RGB image
+        if (bytes_per_pixel==1 || (!bytes_per_pixel && stmax<256)) { // Binary PPM 8 bits
+          CImg<ucharT> buf(buf_size);
+          for (long to_write = _width*_height; to_write>0; ) {
+            const unsigned long N = cimg::min((unsigned long)to_write,buf_size/3);
+            unsigned char *ptrd = buf._data;
+            for (long i = (long)N; i>0; --i) {
+              *(ptrd++) = (unsigned char)*(ptr_r++);
+              *(ptrd++) = (unsigned char)*(ptr_g++);
+              *(ptrd++) = (unsigned char)*(ptr_b++);
+            }
+            cimg::fwrite(buf._data,3*N,nfile);
+            to_write-=N;
+          }
+        } else {             // Binary PPM 16 bits
+          CImg<ushortT> buf(buf_size);
+          for (long to_write = _width*_height; to_write>0; ) {
+            const unsigned long N = cimg::min((unsigned long)to_write,buf_size/3);
+            unsigned short *ptrd = buf._data;
+            for (long i = (long)N; i>0; --i) {
+              *(ptrd++) = (unsigned short)*(ptr_r++);
+              *(ptrd++) = (unsigned short)*(ptr_g++);
+              *(ptrd++) = (unsigned short)*(ptr_b++);
+            }
+            if (!cimg::endianness()) cimg::invert_endianness(buf._data,buf_size);
+            cimg::fwrite(buf._data,3*N,nfile);
+            to_write-=N;
+          }
+        }
+      }
+      }
+      if (!file) cimg::fclose(nfile);
+      return *this;
+    }
+
+    //! Save the image as a PNK file (PINK library extension of PGM).
+    const CImg<T>& save_pnk(const char *const filename) const {
+      return _save_pnk(0,filename);
+    }
+
+    //! Save the image as a PNk file (PINK library extension of PGM).
+    const CImg<T>& save_pnk(std::FILE *const file) const {
+      return _save_pnk(file,0);
+    }
+
+    // Save the image as a PNK file (internal function).
+    const CImg<T>& _save_pnk(std::FILE *const file, const char *const filename) const {
+      if (!file && !filename)
+        throw CImgArgumentException(_cimg_instance
+                                    "save_pnk() : Specified filename is (null).",
+                                    cimg_instance);
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "save_pnk() : Empty instance, for file '%s'.",
+                                    cimg_instance,
+                                    filename?filename:"(FILE*)");
+      if (_spectrum>1)
+        cimg::warn(_cimg_instance
+                   "save_pnk() : Instance is multispectral, only the first channel will be saved in file '%s'.",
+                   cimg_instance,
+                   filename?filename:"(FILE*)");
+
+      const unsigned long buf_size = cimg::min(1024*1024LU,_width*_height*_depth);
+      std::FILE *const nfile = file?file:cimg::fopen(filename,"wb");
+      const T *ptr = data(0,0,0,0);
+
+      if (!cimg::type<T>::is_float() && sizeof(T)==1 && _depth<2) _save_pnm(file,filename,0); // Can be saved as regular PNM file.
+      else if (!cimg::type<T>::is_float() && sizeof(T)==1) { // Save as extended P5 file : Binary byte-valued 3d.
+        std::fprintf(nfile,"P5\n%u %u %u\n255\n",_width,_height,_depth);
+        CImg<ucharT> buf(buf_size);
+        for (long to_write = _width*_height*_depth; to_write>0; ) {
+          const unsigned long N = cimg::min((unsigned long)to_write,buf_size);
+          unsigned char *ptrd = buf._data;
+          for (long i = (long)N; i>0; --i) *(ptrd++) = (unsigned char)*(ptr++);
+          cimg::fwrite(buf._data,N,nfile);
+          to_write-=N;
+        }
+      } else if (!cimg::type<T>::is_float()) { // Save as P8 : Binary int32-valued 3d.
+        if (_depth>1) std::fprintf(nfile,"P8\n%u %u %u\n%d\n",_width,_height,_depth,(int)max());
+        else std::fprintf(nfile,"P8\n%u %u\n%d\n",_width,_height,(int)max());
+        CImg<intT> buf(buf_size);
+        for (long to_write = _width*_height*_depth; to_write>0; ) {
+          const unsigned long N = cimg::min((unsigned long)to_write,buf_size);
+          int *ptrd = buf._data;
+          for (long i = (long)N; i>0; --i) *(ptrd++) = (int)*(ptr++);
+          cimg::fwrite(buf._data,N,nfile);
+          to_write-=N;
+        }
+      } else { // Save as P9 : Binary float-valued 3d.
+        if (_depth>1) std::fprintf(nfile,"P9\n%u %u %u\n%g\n",_width,_height,_depth,(double)max());
+        else std::fprintf(nfile,"P9\n%u %u\n%g\n",_width,_height,(double)max());
+        CImg<floatT> buf(buf_size);
+        for (long to_write = _width*_height*_depth; to_write>0; ) {
+          const unsigned long N = cimg::min((unsigned long)to_write,buf_size);
+          float *ptrd = buf._data;
+          for (long i = (long)N; i>0; --i) *(ptrd++) = (float)*(ptr++);
+          cimg::fwrite(buf._data,N,nfile);
+          to_write-=N;
+        }
+      }
+
+      if (!file) cimg::fclose(nfile);
+      return *this;
+    }
+
+    //! Save the image as a PFM file.
+    const CImg<T>& save_pfm(const char *const filename) const {
+      return _save_pfm(0,filename);
+    }
+
+    //! Save the image as a PFM file.
+    const CImg<T>& save_pfm(std::FILE *const file) const {
+      return _save_pfm(file,0);
+    }
+
+    // Save the image as a PFM file (internal function).
+    const CImg<T>& _save_pfm(std::FILE *const file, const char *const filename) const {
+      if (!file && !filename)
+        throw CImgArgumentException(_cimg_instance
+                                    "save_pfm() : Specified filename is (null).",
+                                    cimg_instance);
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "save_pfm() : Empty instance, for file '%s'.",
+                                    cimg_instance,
+                                    filename?filename:"(FILE*)");
+      if (_depth>1)
+        cimg::warn(_cimg_instance
+                   "save_pfm() : Instance is volumetric, only the first slice will be saved in file '%s'.",
+                   cimg_instance,
+                   filename?filename:"(FILE*)");
+
+      if (_spectrum>3)
+        cimg::warn(_cimg_instance
+                   "save_pfm() : image instance is multispectral, only the three first channels will be saved in file '%s'.",
+                   cimg_instance,
+                   filename?filename:"(FILE*)");
+
+      std::FILE *const nfile = file?file:cimg::fopen(filename,"wb");
+      const T
+        *ptr_r = data(0,0,0,0),
+        *ptr_g = (_spectrum>=2)?data(0,0,0,1):0,
+        *ptr_b = (_spectrum>=3)?data(0,0,0,2):0;
+      const unsigned int buf_size = cimg::min(1024*1024U,_width*_height*(_spectrum==1?1:3));
+
+      std::fprintf(nfile,"P%c\n%u %u\n1.0\n",
+                   (_spectrum==1?'f':'F'),_width,_height);
+
+      switch (_spectrum) {
+      case 1 : { // Scalar image
+        CImg<floatT> buf(buf_size);
+        for (int to_write = _width*_height; to_write>0; ) {
+          const unsigned int N = cimg::min((unsigned int)to_write,buf_size);
+          float *ptrd = buf._data;
+          for (int i = (int)N; i>0; --i) *(ptrd++) = (float)*(ptr_r++);
+          if (!cimg::endianness()) cimg::invert_endianness(buf._data,buf_size);
+          cimg::fwrite(buf._data,N,nfile);
+          to_write-=N;
+        }
+      } break;
+      case 2 : { // RG image
+        CImg<floatT> buf(buf_size);
+        for (int to_write = _width*_height; to_write>0; ) {
+          const unsigned int N = cimg::min((unsigned int)to_write,buf_size/3);
+          float *ptrd = buf._data;
+          for (int i = (int)N; i>0; --i) {
+            *(ptrd++) = (float)*(ptr_r++);
+            *(ptrd++) = (float)*(ptr_g++);
+            *(ptrd++) = 0;
+          }
+          if (!cimg::endianness()) cimg::invert_endianness(buf._data,buf_size);
+          cimg::fwrite(buf._data,3*N,nfile);
+          to_write-=N;
+        }
+      } break;
+      default : { // RGB image
+        CImg<floatT> buf(buf_size);
+        for (int to_write = _width*_height; to_write>0; ) {
+          const unsigned int N = cimg::min((unsigned int)to_write,buf_size/3);
+          float *ptrd = buf._data;
+          for (int i = (int)N; i>0; --i) {
+            *(ptrd++) = (float)*(ptr_r++);
+            *(ptrd++) = (float)*(ptr_g++);
+            *(ptrd++) = (float)*(ptr_b++);
+          }
+          if (!cimg::endianness()) cimg::invert_endianness(buf._data,buf_size);
+          cimg::fwrite(buf._data,3*N,nfile);
+          to_write-=N;
+        }
+      }
+      }
+      if (!file) cimg::fclose(nfile);
+      return *this;
+    }
+
+    // Save the image as a RGB file (internal).
+    const CImg<T>& _save_rgb(std::FILE *const file, const char *const filename) const {
+      if (!file && !filename)
+        throw CImgArgumentException(_cimg_instance
+                                    "save_rgb() : Specified filename is (null).",
+                                    cimg_instance);
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "save_rgb() : Empty instance, for file '%s'.",
+                                    cimg_instance,
+                                    filename?filename:"(FILE*)");
+      if (_spectrum!=3)
+        cimg::warn(_cimg_instance
+                   "save_rgb() : image instance has not exactly 3 channels, for file '%s'.",
+                   cimg_instance,
+                   filename?filename:"(FILE*)");
+
+      std::FILE *const nfile = file?file:cimg::fopen(filename,"wb");
+      const unsigned int wh = _width*_height;
+      unsigned char *const buffer = new unsigned char[3*wh], *nbuffer = buffer;
+      const T
+        *ptr1 = data(0,0,0,0),
+        *ptr2 = _spectrum>1?data(0,0,0,1):0,
+        *ptr3 = _spectrum>2?data(0,0,0,2):0;
+      switch (_spectrum) {
+      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(std::FILE *const file) const {
+      return _save_rgb(file,0);
+    }
+
+    // Save the image as a RGBA file (internal).
+    const CImg<T>& _save_rgba(std::FILE *const file, const char *const filename) const {
+      if (!file && !filename)
+        throw CImgArgumentException(_cimg_instance
+                                    "save_rgba() : Specified filename is (null).",
+                                    cimg_instance);
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "save_rgba() : Empty instance, for file '%s'.",
+                                    cimg_instance,
+                                    filename?filename:"(FILE*)");
+      if (_spectrum!=4)
+        cimg::warn(_cimg_instance
+                   "save_rgba() : image instance has not exactly 4 channels, for file '%s'.",
+                   cimg_instance,
+                   filename?filename:"(FILE*)");
+
+      std::FILE *const nfile = file?file:cimg::fopen(filename,"wb");
+      const unsigned int wh = _width*_height;
+      unsigned char *const buffer = new unsigned char[4*wh], *nbuffer = buffer;
+      const T
+        *ptr1 = data(0,0,0,0),
+        *ptr2 = _spectrum>1?data(0,0,0,1):0,
+        *ptr3 = _spectrum>2?data(0,0,0,2):0,
+        *ptr4 = _spectrum>3?data(0,0,0,3):0;
+      switch (_spectrum) {
+      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(std::FILE *const file) const {
+      return _save_rgba(file,0);
+    }
+
+    // Save a plane into a tiff file
+#ifdef cimg_use_tiff
+
+#define _cimg_save_tiff(types,typed,compression) \
+    if (!std::strcmp(types,pixel_type())) { const typed foo = (typed)0; return _save_tiff(tif,directory,foo,compression); }
+
+    template<typename t>
+    const CImg<T>& _save_tiff(TIFF *tif, const unsigned int directory, const t& pixel_t, const unsigned int compression) const {
+      if (is_empty() || !tif || pixel_t) return *this;
+      const char *const filename = TIFFFileName(tif);
+      uint32 rowsperstrip = (uint32)-1;
+      uint16 spp = _spectrum, bpp = sizeof(t)*8, photometric;
+      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?(compression-1):COMPRESSION_NONE);
+      rowsperstrip = TIFFDefaultStripSize(tif,rowsperstrip);
+      TIFFSetField(tif,TIFFTAG_ROWSPERSTRIP,rowsperstrip);
+      TIFFSetField(tif,TIFFTAG_FILLORDER,FILLORDER_MSB2LSB);
+      TIFFSetField(tif,TIFFTAG_SOFTWARE,"CImg");
+      t *const 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++] = (t)(*this)(cc,row + rr,0,vv);
+          if (TIFFWriteEncodedStrip(tif,strip,buf,i*sizeof(t))<0)
+            throw CImgIOException(_cimg_instance
+                                  "save_tiff() : Invalid strip writing when saving file '%s'.",
+                                  cimg_instance,
+                                  filename?filename:"(FILE*)");
+        }
+        _TIFFfree(buf);
+      }
+      TIFFWriteDirectory(tif);
+      return (*this);
+    }
+
+    const CImg<T>& _save_tiff(TIFF *tif, const unsigned int directory, const unsigned int compression) const {
+      _cimg_save_tiff("bool",unsigned char,compression);
+      _cimg_save_tiff("char",char,compression);
+      _cimg_save_tiff("unsigned char",unsigned char,compression);
+      _cimg_save_tiff("short",short,compression);
+      _cimg_save_tiff("unsigned short",unsigned short,compression);
+      _cimg_save_tiff("int",int,compression);
+      _cimg_save_tiff("unsigned int",unsigned int,compression);
+      _cimg_save_tiff("long",int,compression);
+      _cimg_save_tiff("unsigned long",unsigned int,compression);
+      _cimg_save_tiff("float",float,compression);
+      _cimg_save_tiff("double",float,compression);
+      const char *const filename = TIFFFileName(tif);
+      throw CImgInstanceException(_cimg_instance
+                                  "save_tiff() : Unsupported pixel type '%s' for file '%s'.",
+                                  cimg_instance,
+                                  pixel_type(),filename?filename:"(FILE*)");
+      return *this;
+    }
+#endif
+
+    //! Save a file in TIFF format.
+    /**
+       - libtiff support is enabled by defining the precompilation
+        directive cimg_use_tif.
+
+       - When libtiff is enabled, 2D and 3D (multipage) several
+        channel per pixel are supported for
+        char,uchar,short,ushort,float and double pixel type.
+
+       - If cimg_use_tif is not defined at compilation time the
+        function uses CImg<T>&save_other(const char*).
+
+       \param compression 1:None, 2:CCITTRLE, 3:CCITTFAX3, 4:CCITTFAX4, 5:LZW, 6:JPEG
+
+       \see CImg<T>& save_other(const char*)
+       \see CImg<T>& load_tiff(const char*)
+     **/
+    const CImg<T>& save_tiff(const char *const filename, const unsigned int compression=0) const {
+      if (!filename)
+        throw CImgArgumentException(_cimg_instance
+                                    "save_tiff() : Specified filename is (null).",
+                                    cimg_instance);
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "save_tiff() : Empty instance, for file '%s'.",
+                                    cimg_instance,
+                                    filename);
+
+#ifdef cimg_use_tiff
+      TIFF *tif = TIFFOpen(filename,"w");
+      if (tif) {
+        cimg_forZ(*this,z) get_slice(z)._save_tiff(tif,z,compression);
+        TIFFClose(tif);
+      } else throw CImgIOException(_cimg_instance
+                                   "save_tiff() : Failed to open file '%s' for writing.",
+                                   cimg_instance,
+                                   filename);
+#else
+      cimg::unused(compression);
+      return save_other(filename);
+#endif
+      return *this;
+    }
+
+    //! Save the image as a MINC2 file.
+    // (Original code by Haz-Edine Assemlal).
+    const CImg<T>& save_minc2(const char *const filename) const {
+      if (!filename)
+        throw CImgArgumentException(_cimg_instance
+                                    "save_minc2() : Specified filename is (null).",
+                                    cimg_instance);
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "save_minc2() : Empty instance, for file '%s'.",
+                                    cimg_instance,
+                                    filename);
+#ifndef cimg_use_minc2
+      return save_other(filename);
+#else
+      int nb_useful_dims = 0;
+      if (width()!=1) ++nb_useful_dims;
+      if (height()!=1) ++nb_useful_dims;
+      if (depth()!=1) ++nb_useful_dims;
+      if (spectrum()!=1) ++nb_useful_dims;
+
+      mihandle_t hvol;
+      midimhandle_t *const hdims = (midimhandle_t*)std::malloc(nb_useful_dims*sizeof(midimhandle_t));
+      CImg<ulongT>
+        start(nb_useful_dims,1,1,1,0),
+        count(nb_useful_dims,1,1,1,0);
+      CImg<floatT> output(*this);
+      const char* dim_name[] = {"xspace", "yspace", "zspace", "time"};
+      const midimclass_t dim_class[] = { MI_DIMCLASS_SPATIAL,MI_DIMCLASS_SPATIAL,MI_DIMCLASS_SPATIAL,MI_DIMCLASS_TIME };
+      const midimattr_t dim_attr = MI_DIMATTR_REGULARLY_SAMPLED;
+
+      // Strange stuffs here
+      /* The conversion from CImg data to Minc works only for
+       * specific orders (increasing dimensions and data in decreasing)
+       */
+      CImg<charT> permutations, permutations_inv;
+      CImg<uintT>
+        dim_vec = CImg<uintT>::vector(width(),height(),depth(),spectrum()),
+        dim_sort = dim_vec.get_sort(permutations,true);
+      dim_vec.get_sort(permutations_inv,false);
+
+      int d = 0;
+      cimg_foroff(dim_sort,dv) {
+        if (dim_sort(dv)<=1) continue;
+        micreate_dimension(dim_name[(int)permutations(dv)],dim_class[(int)permutations(dv)],
+                           dim_attr,dim_sort(dv),&hdims[d]);
+        count(d++) = dim_sort(dv);
+      }
+      cimg_for(permutations_inv,ptr,char) if (*ptr<3) (*ptr)+='x'; else *ptr='c';
+      output.permute_axes(permutations_inv.append(CImg<uintT>::vector(0),'y').data()).mirror('y');
+
+      // End of strange stuffs.
+      micreate_volume(filename,nb_useful_dims,hdims,MI_TYPE_FLOAT,MI_CLASS_REAL,0,&hvol);
+      micreate_volume_image(hvol);
+      miset_volume_range(hvol,max(),min());
+      miset_voxel_value_hyperslab(hvol,MI_TYPE_FLOAT,start,count,output.data());
+      miclose_volume(hvol);
+      mifree_dimension_handle(*hdims);
+      return *this;
+#endif
+    }
+
+    //! 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 (!filename)
+        throw CImgArgumentException(_cimg_instance
+                                    "save_analyze() : Specified filename is (null).",
+                                    cimg_instance);
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "save_analyze() : Empty instance, for file '%s'.",
+                                    cimg_instance,
+                                    filename);
+
+      std::FILE *file;
+      char header[348] = { 0 }, hname[1024] = { 0 }, iname[1024] = { 0 };
+      const char *const ext = cimg::split_filename(filename);
+      short datatype=-1;
+      std::memset(header,0,348);
+      if (!*ext) { cimg_snprintf(hname,sizeof(hname),"%s.hdr",filename); cimg_snprintf(iname,sizeof(iname),"%s.img",filename); }
+      if (!cimg::strncasecmp(ext,"hdr",3)) {
+        std::strcpy(hname,filename); std::strncpy(iname,filename,sizeof(iname)-1); std::sprintf(iname + std::strlen(iname)-3,"img");
+      }
+      if (!cimg::strncasecmp(ext,"img",3)) {
+        std::strcpy(hname,filename); std::strncpy(iname,filename,sizeof(iname)-1); std::sprintf(hname + std::strlen(iname)-3,"hdr");
+      }
+      if (!cimg::strncasecmp(ext,"nii",3)) {
+        std::strncpy(hname,filename,sizeof(hname)-1); *iname = 0;
+      }
+      int *const iheader = (int*)header;
+      *iheader = 348;
+      std::strcpy(header + 4,"CImg");
+      std::strcpy(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] = _spectrum;
+      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_instance
+                              "save_analyze() : Unsupported pixel type '%s' for file '%s'.",
+                              cimg_instance,
+                              pixel_type(),filename);
+
+      ((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) { 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 compression=false) const {
+      CImgList<T>(*this,true).save_cimg(filename,compression);
+      return *this;
+    }
+
+    // Save the image as a .cimg file.
+    const CImg<T>& save_cimg(std::FILE *const file, const bool compression=false) const {
+      CImgList<T>(*this,true).save_cimg(file,compression);
+      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 c0) const {
+      CImgList<T>(*this,true).save_cimg(filename,n0,x0,y0,z0,c0);
+      return *this;
+    }
+
+    //! Insert the image into an existing .cimg file, at specified coordinates.
+    const CImg<T>& save_cimg(std::FILE *const file,
+                             const unsigned int n0,
+                             const unsigned int x0, const unsigned int y0,
+                             const unsigned int z0, const unsigned int c0) const {
+      CImgList<T>(*this,true).save_cimg(file,n0,x0,y0,z0,c0);
+      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 dc=1) {
+      return CImgList<T>::save_empty_cimg(filename,1,dx,dy,dz,dc);
+    }
+
+    //! Save an empty .cimg file with specified dimensions.
+    static void save_empty_cimg(std::FILE *const file,
+                                const unsigned int dx, const unsigned int dy=1,
+                                const unsigned int dz=1, const unsigned int dc=1) {
+      return CImgList<T>::save_empty_cimg(file,1,dx,dy,dz,dc);
+    }
+
+    // Save the image as an INRIMAGE-4 file (internal).
+    const CImg<T>& _save_inr(std::FILE *const file, const char *const filename, const float *const voxsize) const {
+      if (!file && !filename)
+        throw CImgArgumentException(_cimg_instance
+                                    "save_inr() : Specified filename is (null).",
+                                    cimg_instance);
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "save_inr() : Empty instance, for file '%s'.",
+                                    cimg_instance,
+                                    filename?filename:"(FILE*)");
+
+      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_instance
+                              "save_inr() : Unsupported pixel type '%s' for file '%s'",
+                              cimg_instance,
+                              pixel_type(),filename?filename:"(FILE*)");
+
+      std::FILE *const nfile = file?file:cimg::fopen(filename,"wb");
+      char header[257] = { 0 };
+      int err = cimg_snprintf(header,sizeof(header),"#INRIMAGE-4#{\nXDIM=%u\nYDIM=%u\nZDIM=%u\nVDIM=%u\n",_width,_height,_depth,_spectrum);
+      if (voxsize) err+=std::sprintf(header + err,"VX=%g\nVY=%g\nVZ=%g\n",voxsize[0],voxsize[1],voxsize[2]);
+      err+=std::sprintf(header + err,"TYPE=%s\nCPU=%s\n",inrtype,cimg::endianness()?"sun":"decm");
+      std::memset(header + err,'\n',252 - err);
+      std::memcpy(header + 252,"##}\n",4);
+      cimg::fwrite(header,256,nfile);
+      cimg_forXYZ(*this,x,y,z) cimg_forC(*this,c) cimg::fwrite(&((*this)(x,y,z,c)),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(std::FILE *const file, const float *const voxsize=0) const {
+      return _save_inr(file,0,voxsize);
+    }
+
+    //! Save the image as a EXR file.
+    const CImg<T>& save_exr(const char *const filename) const {
+      if (!filename)
+        throw CImgArgumentException(_cimg_instance
+                                    "save_exr() : Specified filename is (null).",
+                                    cimg_instance);
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "save_exr() : Empty instance, for file '%s'.",
+                                    cimg_instance,
+                                    filename);
+      if (_depth>1)
+        cimg::warn(_cimg_instance
+                   "save_exr() : Instance is volumetric, only the first slice will be saved in file '%s'.",
+                   cimg_instance,
+                   filename);
+
+#ifndef cimg_use_openexr
+      return save_other(filename);
+#else
+      Imf::Rgba *const ptrd0 = new Imf::Rgba[_width*_height], *ptrd = ptrd0, rgba;
+      switch (_spectrum) {
+      case 1 : { // Grayscale image.
+        for (const T *ptr_r = data(), *const ptr_e = ptr_r + _width*_height; ptr_r<ptr_e;) {
+          rgba.r = rgba.g = rgba.b = (half)(*(ptr_r++));
+          rgba.a = (half)1;
+          *(ptrd++) = rgba;
+        }
+      } break;
+      case 2 : { // RG image.
+        for (const T *ptr_r = data(), *ptr_g = data(0,0,0,1), *const ptr_e = ptr_r + _width*_height; ptr_r<ptr_e; ) {
+          rgba.r = (half)(*(ptr_r++));
+          rgba.g = (half)(*(ptr_g++));
+          rgba.b = (half)0;
+          rgba.a = (half)1;
+          *(ptrd++) = rgba;
+        }
+      } break;
+      case 3 : { // RGB image.
+        for (const T *ptr_r = data(), *ptr_g = data(0,0,0,1), *ptr_b = data(0,0,0,2), *const ptr_e = ptr_r + _width*_height; ptr_r<ptr_e;) {
+          rgba.r = (half)(*(ptr_r++));
+          rgba.g = (half)(*(ptr_g++));
+          rgba.b = (half)(*(ptr_b++));
+          rgba.a = (half)1;
+          *(ptrd++) = rgba;
+        }
+      } break;
+      default : { // RGBA image.
+        for (const T *ptr_r = data(), *ptr_g = data(0,0,0,1), *ptr_b = data(0,0,0,2), *ptr_a = data(0,0,0,3),
+               *const ptr_e = ptr_r + _width*_height; ptr_r<ptr_e;) {
+          rgba.r = (half)(*(ptr_r++));
+          rgba.g = (half)(*(ptr_g++));
+          rgba.b = (half)(*(ptr_b++));
+          rgba.a = (half)(*(ptr_a++));
+          *(ptrd++) = rgba;
+        }
+      } break;
+      }
+      Imf::RgbaOutputFile outFile(filename,_width,_height,
+                                  _spectrum==1?Imf::WRITE_Y:_spectrum==2?Imf::WRITE_YA:_spectrum==3?Imf::WRITE_RGB:Imf::WRITE_RGBA);
+      outFile.setFrameBuffer(ptrd0,1,_width);
+      outFile.writePixels(_height);
+      delete[] ptrd0;
+      return *this;
+#endif
+    }
+
+    // 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] = _spectrum; 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] = _spectrum; dims[1] = _width;  nbdims = 2; }
+      if (id==26 || id==27 || id==29) { dims[0] = _spectrum; dims[1] = _height; dims[2] = _width;  nbdims=3; }
+      if (id==30 || id==31 || id==33) { dims[0] = _spectrum; dims[1] = _depth;  dims[2] = _height; dims[3] = _width; nbdims = 4; }
+      return nbdims;
+    }
+
+    const CImg<T>& _save_pandore(std::FILE *const file, const char *const filename, const unsigned int colorspace) const {
+
+#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==_spectrum):true) && !std::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(unsigned long)==4) { unsigned long ndims[5] = { 0 }; for (int d = 0; d<5; ++d) ndims[d] = (unsigned long)dims[d]; cimg::fwrite(ndims,nbdims,nfile); } \
+        else if (sizeof(unsigned int)==4) { unsigned int ndims[5] = { 0 }; for (int d = 0; d<5; ++d) ndims[d] = (unsigned int)dims[d]; cimg::fwrite(ndims,nbdims,nfile); } \
+        else if (sizeof(unsigned short)==4) { unsigned short ndims[5] = { 0 }; for (int d = 0; d<5; ++d) ndims[d] = (unsigned short)dims[d]; cimg::fwrite(ndims,nbdims,nfile); } \
+        else throw CImgIOException(_cimg_instance \
+                                   "save_pandore() : Unsupported datatype for file '%s'.",\
+                                   cimg_instance, \
+                                   filename?filename:"(FILE*)"); \
+        if (id==2 || id==5 || id==8 || id==16 || id==19 || id==22 || id==26 || id==30) { \
+          __cimg_save_pandore_case(unsigned char); \
+        } else if (id==3 || id==6 || id==9 || id==17 || id==20 || id==23 || id==27 || id==31) { \
+          if (sizeof(unsigned long)==4) { __cimg_save_pandore_case(unsigned long); } \
+          else if (sizeof(unsigned int)==4) { __cimg_save_pandore_case(unsigned int); } \
+          else if (sizeof(unsigned short)==4) { __cimg_save_pandore_case(unsigned short); } \
+          else throw CImgIOException(_cimg_instance \
+                                     "save_pandore() : Unsupported datatype for file '%s'.",\
+                                     cimg_instance, \
+                                     filename?filename:"(FILE*)"); \
+        } 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_instance \
+                                     "save_pandore() : Unsupported datatype for file '%s'.",\
+                                     cimg_instance, \
+                                     filename?filename:"(FILE*)"); \
+        } \
+        saved = true; \
+      }
+
+      if (!file && !filename)
+        throw CImgArgumentException(_cimg_instance
+                                    "save_pandore() : Specified filename is (null).",
+                                    cimg_instance);
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "save_pandore() : Empty instance, for file '%s'.",
+                                    cimg_instance,
+                                    filename?filename:"(FILE*)");
+
+      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] = { 0 };
+      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(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(std::FILE *const file, const char *const filename, const bool multiplexed) const {
+      if (!file && !filename)
+        throw CImgArgumentException(_cimg_instance
+                                    "save_raw() : Specified filename is (null).",
+                                    cimg_instance);
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "save_raw() : empty instance, for file '%s'.",
+                                    cimg_instance,
+                                    filename?filename:"(FILE*)");
+
+      std::FILE *const nfile = file?file:cimg::fopen(filename,"wb");
+      if (!multiplexed) cimg::fwrite(_data,size(),nfile);
+      else {
+        CImg<T> buf(_spectrum);
+        cimg_forXYZ(*this,x,y,z) {
+          cimg_forC(*this,c) buf[c] = (*this)(x,y,z,c);
+          cimg::fwrite(buf._data,_spectrum,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(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 unsigned int bitrate=2048) const {
+      if (!filename)
+        throw CImgArgumentException(_cimg_instance
+                                    "save_ffmpeg() : Specified filename is (null).",
+                                    cimg_instance);
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "save_ffmpeg() : Empty instance, for file '%s'.",
+                                    cimg_instance,
+                                    filename);
+      if (!fps)
+        throw CImgArgumentException(_cimg_instance
+                                    "save_ffmpeg() : Invalid specified framerate 0, for file '%s'.",
+                                    cimg_instance,
+                                    filename);
+
+#ifndef cimg_use_ffmpeg
+      return save_ffmpeg_external(filename,first_frame,last_frame,0,fps,bitrate);
+#else
+      get_split('z').save_ffmpeg(filename,first_frame,last_frame,fps,bitrate);
+#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(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(std::FILE *const file, const char *const filename,
+                             const CImgList<tf>& primitives, const CImgList<tc>& colors) const {
+      if (!file && !filename)
+        throw CImgArgumentException(_cimg_instance
+                                    "save_off() : Specified filename is (null).",
+                                    cimg_instance);
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "save_off() : Empty instance, for file '%s'.",
+                                    cimg_instance,
+                                    filename?filename:"(FILE*)");
+
+      CImgList<T> opacities;
+      char error_message[1024] = { 0 };
+      if (!is_object3d(primitives,colors,opacities,true,error_message))
+        throw CImgInstanceException(_cimg_instance
+                                    "save_off() : Invalid specified 3d object, for file '%s' (%s).",
+                                    cimg_instance,
+                                    filename?filename:"(FILE*)",error_message);
+
+      const CImg<tc> default_color(1,3,1,1,200);
+      std::FILE *const nfile = file?file:cimg::fopen(filename,"w");
+      unsigned int supported_primitives = 0;
+      cimglist_for(primitives,l) if (primitives[l].size()!=5) ++supported_primitives;
+      std::fprintf(nfile,"OFF\n%u %u %u\n",_width,supported_primitives,3*primitives._width);
+      cimg_forX(*this,i) std::fprintf(nfile,"%f %f %f\n",(float)((*this)(i,0)),(float)((*this)(i,1)),(float)((*this)(i,2)));
+      cimglist_for(primitives,l) {
+        const CImg<tc>& color = l<colors.width()?colors[l]:default_color;
+        const unsigned int psiz = primitives[l].size(), csiz = color.size();
+        const float r = color[0]/255.0f, g = (csiz>1?color[1]:r)/255.0f, b = (csiz>2?color[2]:g)/255.0f;
+        switch (psiz) {
+        case 1 : std::fprintf(nfile,"1 %u %f %f %f\n",(unsigned int)primitives(l,0),r,g,b); break;
+        case 2 : 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 : 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 : 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;
+        case 5 : 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 6 : {
+          const unsigned int xt = (unsigned int)primitives(l,2), yt = (unsigned int)primitives(l,3);
+          const float rt = color.atXY(xt,yt,0)/255.0f, gt = (csiz>1?color.atXY(xt,yt,1):r)/255.0f, bt = (csiz>2?color.atXY(xt,yt,2):g)/255.0f;
+          std::fprintf(nfile,"2 %u %u %f %f %f\n",(unsigned int)primitives(l,0),(unsigned int)primitives(l,1),rt,gt,bt);
+        } break;
+        case 9 : {
+          const unsigned int xt = (unsigned int)primitives(l,3), yt = (unsigned int)primitives(l,4);
+          const float rt = color.atXY(xt,yt,0)/255.0f, gt = (csiz>1?color.atXY(xt,yt,1):r)/255.0f, bt = (csiz>2?color.atXY(xt,yt,2):g)/255.0f;
+          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),rt,gt,bt);
+        } break;
+        case 12 : {
+          const unsigned int xt = (unsigned int)primitives(l,4), yt = (unsigned int)primitives(l,5);
+          const float rt = color.atXY(xt,yt,0)/255.0f, gt = (csiz>1?color.atXY(xt,yt,1):r)/255.0f, bt = (csiz>2?color.atXY(xt,yt,2):g)/255.0f;
+          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),rt,gt,bt);
+        } 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 {
+      return _save_off(0,filename,primitives,colors);
+    }
+
+    //! Save OFF files.
+    template<typename tf, typename tc>
+    const CImg<T>& save_off(std::FILE *const file,
+                            const CImgList<tf>& primitives, const CImgList<tc>& colors) const {
+      return _save_off(file,0,primitives,colors);
+    }
+
+    //! 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=0, const unsigned int fps=25, const unsigned int bitrate=2048) const {
+      if (!filename)
+        throw CImgArgumentException(_cimg_instance
+                                    "save_ffmpeg_external() : Specified filename is (null).",
+                                    cimg_instance);
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "save_ffmpeg_external() : Empty instance, for file '%s'.",
+                                    cimg_instance,
+                                    filename);
+
+      get_split('z').save_ffmpeg_external(filename,first_frame,last_frame,codec,fps,bitrate);
+      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 (!filename)
+        throw CImgArgumentException(_cimg_instance
+                                    "save_graphicsmagick_external() : Specified filename is (null).",
+                                    cimg_instance);
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "save_graphicsmagick_external() : Empty instance, for file '%s'.",
+                                    cimg_instance,
+                                    filename);
+
+      char command[1024] = { 0 }, filetmp[512] = { 0 };
+      std::FILE *file;
+      do {
+        cimg_snprintf(filetmp,sizeof(filetmp),"%s%c%s.%s",cimg::temporary_path(),cimg_file_separator,cimg::filenamerand(),_spectrum==1?"pgm":"ppm");
+        if ((file=std::fopen(filetmp,"rb"))!=0) cimg::fclose(file);
+      } while (file);
+      save_pnm(filetmp);
+      cimg_snprintf(command,sizeof(command),"%s convert -quality %u \"%s\" \"%s\"",cimg::graphicsmagick_path(),quality,filetmp,filename);
+      cimg::system(command);
+      file = std::fopen(filename,"rb");
+      if (!file)
+        throw CImgIOException(_cimg_instance
+                              "save_graphicsmagick_external() : Failed to save file '%s' with external command 'gm'.",
+                              cimg_instance,
+                              filename);
+
+      if (file) cimg::fclose(file);
+      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 CImgArgumentException(_cimg_instance
+                                    "save_gzip_external() : Specified filename is (null).",
+                                    cimg_instance);
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "save_gzip_external() : Empty instance, for file '%s'.",
+                                    cimg_instance,
+                                    filename);
+
+      char command[1024] = { 0 }, filetmp[512] = { 0 }, body[512] = { 0 };
+      const char
+        *ext = cimg::split_filename(filename,body),
+        *ext2 = cimg::split_filename(body,0);
+      std::FILE *file;
+      do {
+        if (!cimg::strcasecmp(ext,"gz")) {
+          if (*ext2) cimg_snprintf(filetmp,sizeof(filetmp),"%s%c%s.%s",cimg::temporary_path(),cimg_file_separator,cimg::filenamerand(),ext2);
+          else cimg_snprintf(filetmp,sizeof(filetmp),"%s%c%s.cimg",cimg::temporary_path(),cimg_file_separator,cimg::filenamerand());
+        } else {
+          if (*ext) cimg_snprintf(filetmp,sizeof(filetmp),"%s%c%s.%s",cimg::temporary_path(),cimg_file_separator,cimg::filenamerand(),ext);
+          else cimg_snprintf(filetmp,sizeof(filetmp),"%s%c%s.cimg",cimg::temporary_path(),cimg_file_separator,cimg::filenamerand());
+        }
+        if ((file=std::fopen(filetmp,"rb"))!=0) cimg::fclose(file);
+      } while (file);
+      save(filetmp);
+      cimg_snprintf(command,sizeof(command),"%s -c %s > \"%s\"",cimg::gzip_path(),filetmp,filename);
+      cimg::system(command);
+      file = std::fopen(filename,"rb");
+      if (!file)
+        throw CImgIOException(_cimg_instance
+                              "save_gzip_external() : Failed to save file '%s' with external command 'gzip'.",
+                              cimg_instance,
+                              filename);
+
+      else cimg::fclose(file);
+      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 (!filename)
+        throw CImgArgumentException(_cimg_instance
+                                    "save_imagemagick_external() : Specified filename is (null).",
+                                    cimg_instance);
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "save_imagemagick_external() : Empty instance, for file '%s'.",
+                                    cimg_instance,
+                                    filename);
+
+      char command[1024] = { 0 }, filetmp[512] = { 0 };
+      std::FILE *file;
+      do {
+        cimg_snprintf(filetmp,sizeof(filetmp),"%s%c%s.%s",cimg::temporary_path(),cimg_file_separator,cimg::filenamerand(),_spectrum==1?"pgm":"ppm");
+        if ((file=std::fopen(filetmp,"rb"))!=0) cimg::fclose(file);
+      } while (file);
+      save_pnm(filetmp);
+      cimg_snprintf(command,sizeof(command),"%s -quality %u \"%s\" \"%s\"",cimg::imagemagick_path(),quality,filetmp,filename);
+      cimg::system(command);
+      file = std::fopen(filename,"rb");
+      if (!file)
+        throw CImgIOException(_cimg_instance
+                              "save_imagemagick_external() : Failed to save file '%s' with external command 'convert'.",
+                              cimg_instance,
+                              filename);
+
+      if (file) cimg::fclose(file);
+      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 (!filename)
+        throw CImgArgumentException(_cimg_instance
+                                    "save_medcon_external() : Specified filename is (null).",
+                                    cimg_instance);
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "save_medcon_external() : Empty instance, for file '%s'.",
+                                    cimg_instance,
+                                    filename);
+
+      char command[1024] = { 0 }, filetmp[512] = { 0 }, body[512] = { 0 };
+      std::FILE *file;
+      do {
+        cimg_snprintf(filetmp,sizeof(filetmp),"%s.hdr",cimg::filenamerand());
+        if ((file=std::fopen(filetmp,"rb"))!=0) cimg::fclose(file);
+      } while (file);
+      save_analyze(filetmp);
+      cimg_snprintf(command,sizeof(command),"%s -w -c dicom -o %s -f %s",cimg::medcon_path(),filename,filetmp);
+      cimg::system(command);
+      std::remove(filetmp);
+      cimg::split_filename(filetmp,body);
+      cimg_snprintf(filetmp,sizeof(filetmp),"%s.img",body);
+      std::remove(filetmp);
+
+      file = std::fopen(filename,"rb");
+      if (!file) {
+        cimg_snprintf(command,sizeof(command),"m000-%s",filename);
+        file = std::fopen(command,"rb");
+        if (!file) {
+          cimg::fclose(cimg::fopen(filename,"r"));
+          throw CImgIOException(_cimg_instance
+                                "save_medcon_external() : Failed to save file '%s' with external command 'medcon'.",
+                                cimg_instance,
+                                filename);
+        }
+      }
+      cimg::fclose(file);
+      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 (!filename)
+        throw CImgArgumentException(_cimg_instance
+                                    "save_other() : Specified filename is (null).",
+                                    cimg_instance);
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "save_other() : Empty instance, for file '%s'.",
+                                    cimg_instance,
+                                    filename);
+
+      const unsigned int omode = 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() = omode;
+      if (!is_saved)
+        throw CImgIOException(_cimg_instance
+                              "save_other() : Failed to save file '%s'. Format is not natively supported, and no external commands succeeded.",
+                              cimg_instance,
+                              filename);
+      return *this;
+    }
+
+    // Return a 40x38 color logo of a 'danger' item (internal).
+    static CImg<T> logo40x38() {
+      static bool first_time = true;
+      static CImg<T> res(40,38,1,3);
+      if (first_time) {
+        const unsigned char *ptrs = cimg::logo40x38;
+        T *ptr1 = res.data(0,0,0,0), *ptr2 = res.data(0,0,0,1), *ptr3 = res.data(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<T> structure
+   #
+   #
+   #
+   #------------------------------------------
+   */
+  //! Represent a list of images CImg<T>.
+  template<typename T>
+  struct CImgList {
+    unsigned int _width, _allocated_width;
+    CImg<T> *_data;
+
+    //! Simple iterator type, to loop through each image of a list instance.
+    /**
+       \note
+       - The \c CImgList<T>::iterator type is defined to be a <tt>CImg<T>*</tt>.
+       - You may use it like this :
+       \code
+       CImgList<> list;   // Assuming this image list is not empty.
+       for (CImgList<>::iterator it=list.begin(); it<list.end(); ++it) (*it).mirror('x');
+       \endcode
+       - Anyway, using loop macro \c cimglist_for will probably generate more concise code :
+       \code
+       cimglist_for(list,l) list[l].mirror('x');
+       \endcode
+       \see const_iterator, \c cimglist_apply.
+    **/
+    typedef CImg<T>* iterator;
+
+    //! Simple const iterator type, to loop through each image of a \c const list instance.
+    /**
+       \note
+       - The \c CImgList<T>::const_iterator type is defined to be a <tt>const CImg<T>*</tt>.
+       - Similar to CImgList<T>::iterator, but for constant list instances.
+       \see iterator.
+    **/
+    typedef const CImg<T>* const_iterator;
+
+    //! Pixel value type.
+    /**
+       Refer to the pixels value type of the images in the list.
+       \note
+       - The \c CImgList<T>::value_type type of a \c CImgList<T> is defined to be a \c T. It is then similar to CImg<T>::value_type.
+       - \c CImgList<T>::value_type is actually not used in %CImg methods. It has been mainly defined for
+         compatibility with STL naming conventions.
+    **/
+    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 / Instance Management
+    //@{
+    //--------------------------------------------------------
+
+    //! Destructor.
+    /**
+       Destroy current list.
+       \note
+       - Call the destructors for each image of the list.
+       - Destroying an empty list does actually nothing.
+       \see CImgList(), assign().
+     **/
+    ~CImgList() {
+      delete[] _data;
+    }
+
+    //! Create an empty list.
+    /**
+       \note Constructing an empty CImgList doesn't allocate extra memory buffer.
+       \see ~CImgList(), assign().
+     **/
+    CImgList():
+      _width(0),_allocated_width(0),_data(0) {}
+
+    //! Create a list containing \c n empty images.
+    /**
+       \param n Number of empty images in the constructed instance.
+       \note Useful when you know by advance the number of images you want to manage, as
+       it will allocate the right amount of memory for the list, without needing for reallocation
+       (that may occur when starting from an empty list and inserting several images in it).
+       \see assign(unsigned int).
+    **/
+    explicit CImgList(const unsigned int n):_width(n) {
+      if (n) _data = new CImg<T>[_allocated_width = cimg::max(16UL,cimg::nearest_pow2(n))];
+      else { _allocated_width = 0; _data = 0; }
+    }
+
+    //! Create a list containing \c n images with specified size.
+    /**
+       \param n Number of images in the constructed instance.
+       \param width Desired images width.
+       \param height Desired images height.
+       \param depth Desired images depth.
+       \param spectrum Desired number of image channels.
+       \see assign(unsigned int,unsigned int,unsigned int,unsigned int,unsigned int).
+    **/
+    CImgList(const unsigned int n, const unsigned int width, const unsigned int height=1,
+             const unsigned int depth=1, const unsigned int spectrum=1):
+      _width(0),_allocated_width(0),_data(0) {
+      assign(n);
+      cimglist_apply(*this,assign)(width,height,depth,spectrum);
+    }
+
+    //! Create a list containing \c n images with specified size, and initialize pixel values.
+    /**
+       \param n Number of images in the constructed instance.
+       \param width Desired images width.
+       \param height Desired images height.
+       \param depth Desired images depth.
+       \param spectrum Desired number of image channels.
+       \param val Value to initialize images pixels.
+       \note
+       Similar to CImgList(unsigned int,unsigned int,unsigned int,unsigned int,unsigned int) with a default value set for all
+       image pixels.
+       \see assign(unsigned int,unsigned int,unsigned int,unsigned int,unsigned int,const T).
+    **/
+    CImgList(const unsigned int n, const unsigned int width, const unsigned int height,
+             const unsigned int depth, const unsigned int spectrum, const T val):
+      _width(0),_allocated_width(0),_data(0) {
+      assign(n);
+      cimglist_apply(*this,assign)(width,height,depth,spectrum,val);
+    }
+
+    //! Create a list containing \c n images with specified size, and initialize pixel values from a sequence of integers.
+    /**
+       \param n Number of images in the constructed instance.
+       \param width Desired images width.
+       \param height Desired images height.
+       \param depth Desired images depth.
+       \param spectrum Desired number of image channels.
+       \param val0 First value of an integers sequence to initialize images pixels.
+       \param val1 Secon value of an integers sequence to initialize images pixels.
+       \note
+       Similar to CImgList(unsigned int,unsigned int,unsigned int,unsigned int,unsigned int,const T) with a sequence of integers to
+       initialize pixel values.
+       \see assign(unsigned int,unsigned int,unsigned int,unsigned int,unsigned int,int,int,...).
+    **/
+    CImgList(const unsigned int n, const unsigned int width, const unsigned int height,
+             const unsigned int depth, const unsigned int spectrum, const int val0, const int val1, ...):
+      _width(0),_allocated_width(0),_data(0) {
+#define _CImgList_stdarg(t) { \
+        assign(n,width,height,depth,spectrum); \
+        const unsigned int siz = width*height*depth*spectrum, 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);
+    }
+
+    //! Create a list containing \c n images with specified size, and initialize pixel values from a sequence of doubles.
+    /**
+       \param n Number of images in the constructed list.
+       \param width Desired images width.
+       \param height Desired images height.
+       \param depth Desired images depth.
+       \param spectrum Desired number of image channels.
+       \param val0 First value of a doubles sequence to initialize images pixels.
+       \param val1 Second value of a doubles sequence to initialize images pixels.
+       \note
+       Similar to CImgList(unsigned int,unsigned int,unsigned int,unsigned int,unsigned int,int,int,...) with a sequence of doubles to
+       initialize pixel values.
+       \see assign(unsigned int,unsigned int,unsigned int,unsigned int,unsigned int,double,double,...).
+    **/
+    CImgList(const unsigned int n, const unsigned int width, const unsigned int height,
+             const unsigned int depth, const unsigned int spectrum, const double val0, const double val1, ...):
+      _width(0),_allocated_width(0),_data(0) {
+      _CImgList_stdarg(double);
+    }
+
+    //! Create a list containing \c n copies of the same input image.
+    /**
+       \param n Number of images in the constructed list.
+       \param img Input image to clone \c n times in the created list.
+       \param is_shared Flag telling if the elements of the list are shared or non-shared copies of \c img.
+       \see assign(unsigned int,const CImg<t>&,bool).
+    **/
+    template<typename t>
+    CImgList(const unsigned int n, const CImg<t>& img, const bool is_shared=false):
+      _width(0),_allocated_width(0),_data(0) {
+      assign(n);
+      cimglist_apply(*this,assign)(img,is_shared);
+    }
+
+    //! Create a list containing one copy of an input image.
+    /**
+       \param img Input image to clone in the constructed list.
+       \param is_shared Flag telling if the element of the list is a shared or non-shared copy of \c img.
+       \see assign(const CImg<t>&,bool).
+     **/
+    template<typename t>
+    explicit CImgList(const CImg<t>& img, const bool is_shared=false):
+      _width(0),_allocated_width(0),_data(0) {
+      assign(1);
+      _data[0].assign(img,is_shared);
+    }
+
+    //! Create a list from two images.
+    /**
+       \param img1 First input image to clone in the constructed list.
+       \param img2 Second input image to clone in the constructed list.
+       \param is_shared Flag telling if the elements of the list are shared or non-shared copies of input images.
+       \see assign(const CImg<t1>&,const CImg<t2>&,bool).
+     **/
+    template<typename t1, typename t2>
+    CImgList(const CImg<t1>& img1, const CImg<t2>& img2, const bool is_shared=false):
+      _width(0),_allocated_width(0),_data(0) {
+      assign(2);
+      _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared);
+    }
+
+    //! Create a list from three images.
+    /**
+       \param img1 First input image to clone in the constructed list.
+       \param img2 Second input image to clone in the constructed list.
+       \param img3 Third input image to clone in the constructed list.
+       \param is_shared Flag telling if the elements of the list are shared or non-shared copies of input images.
+       \see assign(const CImg<t1>&,const CImg<t2>&,const CImg<t3>&,bool).
+    **/
+    template<typename t1, typename t2, typename t3>
+    CImgList(const CImg<t1>& img1, const CImg<t2>& img2, const CImg<t3>& img3, const bool is_shared=false):
+      _width(0),_allocated_width(0),_data(0) {
+      assign(3);
+      _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared); _data[2].assign(img3,is_shared);
+    }
+
+    //! Create a list from four images.
+    /**
+       \param img1 First input image to clone in the constructed list.
+       \param img2 Second input image to clone in the constructed list.
+       \param img3 Third input image to clone in the constructed list.
+       \param img4 Fourth input image to clone in the constructed list.
+       \param is_shared Flag telling if the elements of the list are shared or non-shared copies of input images.
+       \see assign(const CImg<t1>&,const CImg<t2>&,const CImg<t3>&,const CImg<t4>&,bool).
+    **/
+    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 is_shared=false):
+      _width(0),_allocated_width(0),_data(0) {
+      assign(4);
+      _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared); _data[2].assign(img3,is_shared); _data[3].assign(img4,is_shared);
+    }
+
+    //! Create a list from five images.
+    /**
+       \param img1 First input image to clone in the constructed list.
+       \param img2 Second input image to clone in the constructed list.
+       \param img3 Third input image to clone in the constructed list.
+       \param img4 Fourth input image to clone in the constructed list.
+       \param img5 Fifth input image to clone in the constructed list.
+       \param is_shared Flag telling if the elements of the list are shared or non-shared copies of input images.
+       \see assign(const CImg<t1>&,const CImg<t2>&,const CImg<t3>&,const CImg<t4>&,const CImg<t5>&,bool).
+    **/
+    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 is_shared=false):
+      _width(0),_allocated_width(0),_data(0) {
+      assign(5);
+      _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared); _data[2].assign(img3,is_shared); _data[3].assign(img4,is_shared);
+      _data[4].assign(img5,is_shared);
+    }
+
+    //! Create a list from six images.
+    /**
+       \param img1 First input image to clone in the constructed list.
+       \param img2 Second input image to clone in the constructed list.
+       \param img3 Third input image to clone in the constructed list.
+       \param img4 Fourth input image to clone in the constructed list.
+       \param img5 Fifth input image to clone in the constructed list.
+       \param img6 Sixth input image to clone in the constructed list.
+       \param is_shared Flag telling if the elements of the list are shared or non-shared copies of input images.
+       \see assign(const CImg<t1>&,const CImg<t2>&,const CImg<t3>&,const CImg<t4>&,const CImg<t5>&,const CImg<t6>&,bool).
+    **/
+    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 is_shared=false):
+      _width(0),_allocated_width(0),_data(0) {
+      assign(6);
+      _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared); _data[2].assign(img3,is_shared); _data[3].assign(img4,is_shared);
+      _data[4].assign(img5,is_shared); _data[5].assign(img6,is_shared);
+    }
+
+    //! Create a list from seven images.
+    /**
+       \param img1 First input image to clone in the constructed list.
+       \param img2 Second input image to clone in the constructed list.
+       \param img3 Third input image to clone in the constructed list.
+       \param img4 Fourth input image to clone in the constructed list.
+       \param img5 Fifth input image to clone in the constructed list.
+       \param img6 Sixth input image to clone in the constructed list.
+       \param img7 Seventh input image to clone in the constructed list.
+       \param is_shared Flag telling if the elements of the list are shared or non-shared copies of input images.
+       \see assign(const CImg<t1>&,const CImg<t2>&,const CImg<t3>&,const CImg<t4>&,const CImg<t5>&,const CImg<t6>&,const CImg<t7>&,bool).
+    **/
+    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 is_shared=false):
+      _width(0),_allocated_width(0),_data(0) {
+      assign(7);
+      _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared); _data[2].assign(img3,is_shared); _data[3].assign(img4,is_shared);
+      _data[4].assign(img5,is_shared); _data[5].assign(img6,is_shared); _data[6].assign(img7,is_shared);
+    }
+
+    //! Create a list from eight images.
+    /**
+       \param img1 First input image to clone in the constructed list.
+       \param img2 Second input image to clone in the constructed list.
+       \param img3 Third input image to clone in the constructed list.
+       \param img4 Fourth input image to clone in the constructed list.
+       \param img5 Fifth input image to clone in the constructed list.
+       \param img6 Sixth input image to clone in the constructed list.
+       \param img7 Seventh input image to clone in the constructed list.
+       \param img8 Eighth input image to clone in the constructed list.
+       \param is_shared Flag telling if the elements of the list are shared or non-shared copies of input images.
+       \see assign(const CImg<t1>&,const CImg<t2>&,const CImg<t3>&,const CImg<t4>&,const CImg<t5>&,const CImg<t6>&,const CImg<t7>&,const CImg<t8>&,bool).
+    **/
+    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 is_shared=false):
+      _width(0),_allocated_width(0),_data(0) {
+      assign(8);
+      _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared); _data[2].assign(img3,is_shared); _data[3].assign(img4,is_shared);
+      _data[4].assign(img5,is_shared); _data[5].assign(img6,is_shared); _data[6].assign(img7,is_shared); _data[7].assign(img8,is_shared);
+    }
+
+    //! Create a list as a copy of an existing list.
+    /**
+       \param list Input list to copy.
+       \note The shared state of each element of the constructed list is the same as in the input list \c list.
+       \see assign(const CImgList<t>&).
+    **/
+    template<typename t>
+    CImgList(const CImgList<t>& list):_width(0),_allocated_width(0),_data(0) {
+      assign(list._width);
+      cimglist_for(*this,l) _data[l].assign(list[l],false);
+    }
+
+    //! Create a list as a copy of an existing list \specialization.
+    CImgList(const CImgList<T>& list):_width(0),_allocated_width(0),_data(0) {
+      assign(list._width);
+      cimglist_for(*this,l) _data[l].assign(list[l],list[l]._is_shared);
+    }
+
+    //! Create a list as a copy of an existing list, and force the shared state of the list elements.
+    /**
+       \param list Input list to copy.
+       \param is_shared Flag telling if the elements of the list are shared or non-shared copies of input images.
+       \see assign(const CImgList<t>&,bool).
+    **/
+    template<typename t>
+    CImgList(const CImgList<t>& list, const bool is_shared):_width(0),_allocated_width(0),_data(0) {
+      assign(list._width);
+      cimglist_for(*this,l) _data[l].assign(list[l],is_shared);
+    }
+
+    //! Create a list from the content of a file.
+    /**
+       \param filename Filename.
+       \note Some file formats support the storage of multiple images in the same file. In this case, all
+       available images are read and stored in the constructed list.
+       \see assign(const char *const), load(const char *const).
+    **/
+    explicit CImgList(const char *const filename):_width(0),_allocated_width(0),_data(0) {
+      assign(filename);
+    }
+
+    //! Create a list from the content of a display window.
+    /**
+       \param disp Display window to get content from.
+       \note The created list contains only a single image.
+       \see assign(const CImgDisplay&).
+    **/
+    explicit CImgList(const CImgDisplay &disp):_width(0),_allocated_width(0),_data(0) {
+      assign(disp);
+    }
+
+    //! Return a list with elements being shared copies of images in the list instance.
+    /**
+      \note <tt>list2 = list1.get_shared()</tt> is equivalent to <tt>list2.assign(list1,true)</tt>.
+      \see assign(const CImg<T>&,bool).
+    **/
+    CImgList<T> get_shared() {
+      CImgList<T> res(_width);
+      cimglist_for(*this,l) res[l].assign(_data[l],true);
+      return res;
+    }
+
+    //! Return a list with elements being shared copies of images in the list instance \const.
+    /**
+       \note Similar to get_shared() for \c const list instances.
+    **/
+    const CImgList<T> get_shared() const {
+      CImgList<T> res(_width);
+      cimglist_for(*this,l) res[l].assign(_data[l],true);
+      return res;
+    }
+
+    //! Create an empty list \inplace.
+    /**
+       \note Replace the instance by an empty list.
+       \see CImgList().
+    **/
+    CImgList<T>& assign() {
+      delete[] _data;
+      _width = _allocated_width = 0;
+      _data = 0;
+      return *this;
+    }
+
+    //! Create an empty list \inplace.
+    /**
+       Equivalent to assign().
+       \note It has been defined for compatibility with STL naming conventions.
+       \see assign().
+    **/
+    CImgList<T>& clear() {
+      return assign();
+    }
+
+    //! Create a list containing \c n empty images \inplace.
+    /**
+       \param n Number of empty images in the constructed instance.
+       \see CImgList(unsigned int).
+    **/
+    CImgList<T>& assign(const unsigned int n) {
+      if (!n) return assign();
+      if (_allocated_width<n || _allocated_width>(n<<2)) {
+        delete[] _data;
+        _data = new CImg<T>[_allocated_width=cimg::max(16UL,cimg::nearest_pow2(n))];
+      }
+      _width = n;
+      return *this;
+    }
+
+    //! Create a list containing \c n images with specified size \inplace.
+    /**
+       \param n Number of images in the constructed instance.
+       \param width Desired images width.
+       \param height Desired images height.
+       \param depth Desired images depth.
+       \param spectrum Desired number of image channels.
+       \see CImgList(unsigned int,unsigned int,unsigned int,unsigned int,unsigned int).
+    **/
+    CImgList<T>& assign(const unsigned int n, const unsigned int width, const unsigned int height=1,
+                        const unsigned int depth=1, const unsigned int spectrum=1) {
+      assign(n);
+      cimglist_apply(*this,assign)(width,height,depth,spectrum);
+      return *this;
+    }
+
+    //! Create a list containing \c n images with specified size, and initialize pixel values \inplace.
+    /**
+       \param n Number of images in the constructed instance.
+       \param width Desired images width.
+       \param height Desired images height.
+       \param depth Desired images depth.
+       \param spectrum Desired number of image channels.
+       \param val Value to initialize images pixels.
+       \see CImgList(unsigned int,unsigned int,unsigned int,unsigned int,unsigned int,const T).
+    **/
+    CImgList<T>& assign(const unsigned int n, const unsigned int width, const unsigned int height,
+                        const unsigned int depth, const unsigned int spectrum, const T val) {
+      assign(n);
+      cimglist_apply(*this,assign)(width,height,depth,spectrum,val);
+      return *this;
+    }
+
+    //! Create a list containing \c n images with specified size, and initialize pixel values from a sequence of integers \inplace.
+    /**
+       \param n Number of images in the constructed instance.
+       \param width Desired images width.
+       \param height Desired images height.
+       \param depth Desired images depth.
+       \param spectrum Desired number of image channels.
+       \param val0 First value of an integers sequence to initialize images pixels.
+       \param val1 Secon value of an integers sequence to initialize images pixels.
+       \see CImgList(unsigned int,unsigned int,unsigned int,unsigned int,unsigned int,int,int,...).
+    **/
+    CImgList<T>& assign(const unsigned int n, const unsigned int width, const unsigned int height,
+                        const unsigned int depth, const unsigned int spectrum, const int val0, const int val1, ...) {
+      _CImgList_stdarg(int);
+      return *this;
+    }
+
+    //! Create a list containing \c n images with specified size, and initialize pixel values from a sequence of doubles \inplace.
+    /**
+       \param n Number of images in the constructed list.
+       \param width Desired images width.
+       \param height Desired images height.
+       \param depth Desired images depth.
+       \param spectrum Desired number of image channels.
+       \param val0 First value of a doubles sequence to initialize images pixels.
+       \param val1 Second value of a doubles sequence to initialize images pixels.
+       \see CImgList(unsigned int,unsigned int,unsigned int,unsigned int,unsigned int,double,double,...).
+    **/
+    CImgList<T>& assign(const unsigned int n, const unsigned int width, const unsigned int height,
+                        const unsigned int depth, const unsigned int spectrum, const double val0, const double val1, ...) {
+      _CImgList_stdarg(double);
+      return *this;
+    }
+
+    //! Create a list containing \c n copies of the same input image \specialization \inplace.
+    template<typename t>
+    CImgList<T>& assign(const unsigned int n, const CImg<t>& img, const bool is_shared=false) {
+      assign(n);
+      cimglist_apply(*this,assign)(img,is_shared);
+      return *this;
+    }
+
+    //! Create a list containing one copy of an input image \inplace.
+    /**
+       \param img Input image to clone in the constructed list.
+       \param is_shared Flag telling if the element of the list is a shared or non-shared copy of \c img.
+       \see CImgList(const CImg<t>&,bool).
+    **/
+    template<typename t>
+    CImgList<T>& assign(const CImg<t>& img, const bool is_shared=false) {
+      assign(1);
+      _data[0].assign(img,is_shared);
+      return *this;
+    }
+
+    //! Create a list from two images \inplace.
+    /**
+       \param img1 First input image to clone in the constructed list.
+       \param img2 Second input image to clone in the constructed list.
+       \param is_shared Flag telling if the elements of the list are shared or non-shared copies of input images.
+       \see CImgList(const CImg<t1>&,const CImg<t2>&,bool).
+    **/
+    template<typename t1, typename t2>
+    CImgList<T>& assign(const CImg<t1>& img1, const CImg<t2>& img2, const bool is_shared=false) {
+      assign(2);
+      _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared);
+      return *this;
+    }
+
+    //! Create a list from three images \inplace.
+    /**
+       \param img1 First input image to clone in the constructed list.
+       \param img2 Second input image to clone in the constructed list.
+       \param img3 Third input image to clone in the constructed list.
+       \param is_shared Flag telling if the elements of the list are shared or non-shared copies of input images.
+       \see CImgList(const CImg<t1>&,const CImg<t2>&,const CImg<t3>&,bool).
+    **/
+    template<typename t1, typename t2, typename t3>
+    CImgList<T>& assign(const CImg<t1>& img1, const CImg<t2>& img2, const CImg<t3>& img3, const bool is_shared=false) {
+      assign(3);
+      _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared); _data[2].assign(img3,is_shared);
+      return *this;
+    }
+
+    //! Create a list from four images \inplace.
+    /**
+       \param img1 First input image to clone in the constructed list.
+       \param img2 Second input image to clone in the constructed list.
+       \param img3 Third input image to clone in the constructed list.
+       \param img4 Fourth input image to clone in the constructed list.
+       \param is_shared Flag telling if the elements of the list are shared or non-shared copies of input images.
+       \see CImgList(const CImg<t1>&,const CImg<t2>&,const CImg<t3>&,const CImg<t4>&,bool).
+    **/
+    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 is_shared=false) {
+      assign(4);
+      _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared); _data[2].assign(img3,is_shared); _data[3].assign(img4,is_shared);
+      return *this;
+    }
+
+    //! Create a list from five images \inplace.
+    /**
+       \param img1 First input image to clone in the constructed list.
+       \param img2 Second input image to clone in the constructed list.
+       \param img3 Third input image to clone in the constructed list.
+       \param img4 Fourth input image to clone in the constructed list.
+       \param img5 Fifth input image to clone in the constructed list.
+       \param is_shared Flag telling if the elements of the list are shared or non-shared copies of input images.
+       \see CImgList(const CImg<t1>&,const CImg<t2>&,const CImg<t3>&,const CImg<t4>&,const CImg<t5>&,bool).
+    **/
+    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 is_shared=false) {
+      assign(5);
+      _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared); _data[2].assign(img3,is_shared); _data[3].assign(img4,is_shared);
+      _data[4].assign(img5,is_shared);
+      return *this;
+    }
+
+    //! Create a list from six images \inplace.
+    /**
+       \param img1 First input image to clone in the constructed list.
+       \param img2 Second input image to clone in the constructed list.
+       \param img3 Third input image to clone in the constructed list.
+       \param img4 Fourth input image to clone in the constructed list.
+       \param img5 Fifth input image to clone in the constructed list.
+       \param img6 Sixth input image to clone in the constructed list.
+       \param is_shared Flag telling if the elements of the list are shared or non-shared copies of input images.
+       \see CImgList(const CImg<t1>&,const CImg<t2>&,const CImg<t3>&,const CImg<t4>&,const CImg<t5>&,const CImg<t6>&,bool).
+    **/
+    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 is_shared=false) {
+      assign(6);
+      _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared); _data[2].assign(img3,is_shared); _data[3].assign(img4,is_shared);
+      _data[4].assign(img5,is_shared); _data[5].assign(img6,is_shared);
+      return *this;
+    }
+
+    //! Create a list from seven images \inplace.
+    /**
+       \param img1 First input image to clone in the constructed list.
+       \param img2 Second input image to clone in the constructed list.
+       \param img3 Third input image to clone in the constructed list.
+       \param img4 Fourth input image to clone in the constructed list.
+       \param img5 Fifth input image to clone in the constructed list.
+       \param img6 Sixth input image to clone in the constructed list.
+       \param img7 Seventh input image to clone in the constructed list.
+       \param is_shared Flag telling if the elements of the list are shared or non-shared copies of input images.
+       \see CImgList(const CImg<t1>&,const CImg<t2>&,const CImg<t3>&,const CImg<t4>&,const CImg<t5>&,const CImg<t6>&,const CImg<t7>&,bool).
+    **/
+    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 is_shared=false) {
+      assign(7);
+      _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared); _data[2].assign(img3,is_shared); _data[3].assign(img4,is_shared);
+      _data[4].assign(img5,is_shared); _data[5].assign(img6,is_shared); _data[6].assign(img7,is_shared);
+      return *this;
+    }
+
+    //!  Create a list from eight images \inplace.
+    /**
+       \param img1 First input image to clone in the constructed list.
+       \param img2 Second input image to clone in the constructed list.
+       \param img3 Third input image to clone in the constructed list.
+       \param img4 Fourth input image to clone in the constructed list.
+       \param img5 Fifth input image to clone in the constructed list.
+       \param img6 Sixth input image to clone in the constructed list.
+       \param img7 Seventh input image to clone in the constructed list.
+       \param img8 Eighth input image to clone in the constructed list.
+       \param is_shared Flag telling if the elements of the list are shared or non-shared copies of input images.
+       \see CImgList(const CImg<t1>&,const CImg<t2>&,const CImg<t3>&,const CImg<t4>&,const CImg<t5>&,const CImg<t6>&,const CImg<t7>&,const CImg<t8>&,bool).
+    **/
+    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 is_shared=false) {
+      assign(8);
+      _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared); _data[2].assign(img3,is_shared); _data[3].assign(img4,is_shared);
+      _data[4].assign(img5,is_shared); _data[5].assign(img6,is_shared); _data[6].assign(img7,is_shared); _data[7].assign(img8,is_shared);
+      return *this;
+    }
+
+    //! Create a list as a copy of an existing list and force the shared state of the list elements \inplace.
+    /**
+       \param list Input list to copy.
+       \param is_shared Flag telling if the elements of the list are shared or non-shared copies of input images.
+       \see CImgList(const CImgList<t>&,bool).
+    **/
+    template<typename t>
+    CImgList<T>& assign(const CImgList<t>& list, const bool is_shared=false) {
+      cimg::unused(is_shared);
+      assign(list._width);
+      cimglist_for(*this,l) _data[l].assign(list[l],false);
+      return *this;
+    }
+
+    //! Create a list containing \c n copies of the same input image \specialization \inplace.
+    CImgList<T>& assign(const CImgList<T>& list, const bool is_shared=false) {
+      if (this==&list) return *this;
+      CImgList<T> res(list._width);
+      cimglist_for(res,l) res[l].assign(list[l],is_shared);
+      return res.move_to(*this);
+    }
+
+    //! Create a list from the content of a file \inplace.
+    /**
+       \param filename Filename.
+       \see CImgList(const char *const), load(const char *const).
+    **/
+    CImgList<T>& assign(const char *const filename) {
+      return load(filename);
+    }
+
+    //! Create a list from the content of a display window \inplace.
+    /**
+       \param disp Display window to get content from.
+       \see CImgList(const CImgDisplay&).
+    **/
+    CImgList<T>& assign(const CImgDisplay &disp) {
+      return assign(CImg<T>(disp));
+    }
+
+    //! Transfer the content of the list instance into another list.
+    /**
+       \param list Destination list instance.
+       \note When returning, the list instance is empty and the initial content of \c list is destroyed.
+       \see move_to(CImgList<t>&,unsigned int).
+    **/
+    template<typename t>
+    CImgList<t>& move_to(CImgList<t>& list) {
+      list.assign(_width);
+      bool is_one_shared_element = false;
+      cimglist_for(*this,l) is_one_shared_element|=_data[l]._is_shared;
+      if (is_one_shared_element) cimglist_for(*this,l) list[l].assign(_data[l]);
+      else cimglist_for(*this,l) _data[l].move_to(list[l]);
+      assign();
+      return list;
+    }
+
+    //! Transfer the content of the list instance at a specified position in another list.
+    /**
+       \param list Destination list instance.
+       \note When returning, the list instance is empty and the initial content of \c list is preserved
+       (only images indexes may be modified).
+       \see move_to(CImgList<t>&).
+     **/
+    template<typename t>
+    CImgList<t>& move_to(CImgList<t>& list, const unsigned int pos) {
+      if (is_empty()) return list;
+      const unsigned int npos = pos>list._width?list._width:pos;
+      list.insert(_width,npos);
+      bool is_one_shared_element = false;
+      cimglist_for(*this,l) is_one_shared_element|=_data[l]._is_shared;
+      if (is_one_shared_element) cimglist_for(*this,l) list[npos+l].assign(_data[l]);
+      else cimglist_for(*this,l) _data[l].move_to(list[npos+l]);
+      assign();
+      return list;
+    }
+
+    //! Swap all fields between list instance and another list.
+    /**
+       \param list List to swap fields with.
+       \note Can be used to exchange the content of two lists in a fast way.
+       \see CImg<T>::swap(CImg<T>&).
+    **/
+    CImgList<T>& swap(CImgList<T>& list) {
+      cimg::swap(_width,list._width);
+      cimg::swap(_allocated_width,list._allocated_width);
+      cimg::swap(_data,list._data);
+      return list;
+    }
+
+    //! Return a reference to an empty list.
+    /**
+      \note Can be used to define default values in a function taking a CImgList<T> as an argument.
+      \code
+      void f(const CImgList<char>& list=CImgList<char>::empty());
+      \endcode
+    **/
+    static CImgList<T>& empty() {
+      static CImgList<T> _empty;
+      return _empty.assign();
+    }
+
+    //@}
+    //------------------------------------------
+    //
+    //! \name Overloaded Operators
+    //@{
+    //------------------------------------------
+
+    //! Return a reference to the \c pos-th image of the list instance.
+    /**
+       \param pos Indice of the image element to return.
+       \see operator[](unsigned int)
+    **/
+    CImg<T>& operator()(const unsigned int pos) {
+#if cimg_verbosity>=3
+      if (pos>=_width) {
+        cimg::warn(_cimglist_instance
+                   "operator() : Invalid image request, at position [%u].",
+                   cimglist_instance,
+                   pos);
+        return *_data;
+      }
+#endif
+      return _data[pos];
+    }
+
+    //! Return a reference to the \c pos-th image of the list.
+    /**
+       \param pos Indice of the image element to return.
+       \see operator[](unsigned int) const
+    **/
+    const CImg<T>& operator()(const unsigned int pos) const {
+      return const_cast<CImgList<T>*>(this)->operator()(pos);
+    }
+
+    //! Return a reference to the (x,y,z,c) pixel value of the \c pos-th image of the list.
+    /**
+       \param pos Indice of the image element to access.
+       \param x X-coordinate of the pixel location.
+       \param y Y-coordinate of the pixel location.
+       \param z Z-coordinate of the pixel location.
+       \param c C-coordinate of the pixel location.
+       \note <tt>list(n,x,y,z,c)</tt> is equivalent to <tt>list[n](x,y,z,c)</tt>.
+       \see operator()(unsigned int).
+    **/
+    T& operator()(const unsigned int pos, const unsigned int x, const unsigned int y=0,
+                  const unsigned int z=0, const unsigned int c=0) {
+      return (*this)[pos](x,y,z,c);
+    }
+
+    //! Return a reference to the (x,y,z,c) pixel value of the \c pos-th image of the list \const.
+    const T& operator()(const unsigned int pos, const unsigned int x, const unsigned int y=0,
+                        const unsigned int z=0, const unsigned int c=0) const {
+      return (*this)[pos](x,y,z,c);
+    }
+
+    //! Return pointer to the first image of the list.
+    /**
+       \note Images in a list are stored as a buffer of \c CImg<T>.
+    **/
+    operator CImg<T>*() {
+      return _data;
+    }
+
+    //! Return pointer to the first image of the list \const.
+    operator const CImg<T>*() const {
+      return _data;
+    }
+
+    //! Create a list containing one copy of an input image \inplace.
+    /**
+        \param img Input image to clone in the constructed list.
+        \note <tt>list = img;</tt> is equivalent to <tt>list.assign(img);</tt>.
+        \see assign(const CImg<t>&).
+    **/
+    template<typename t>
+    CImgList<T>& operator=(const CImg<t>& img) {
+      return assign(img);
+    }
+
+    //! Create a list as a copy of an existing list.
+    /**
+       \param list Input list to copy.
+       \note <tt>list1 = list2</tt> is equivalent to <tt>list1.assign(list2);</tt>.
+    **/
+    template<typename t>
+    CImgList<T>& operator=(const CImgList<t>& list) {
+      return assign(list);
+    }
+
+    //! Create a list as a copy of an existing list \specialization.
+    CImgList<T>& operator=(const CImgList<T>& list) {
+      return assign(list);
+    }
+
+    //! Create a list from the content of a file \inplace.
+    /**
+       \param filename Filename.
+       \note <tt>list1 = filename</tt> is equivalent to <tt>list1.assign(filename);</tt>.
+       \see assign(const char *const), load(const char *const).
+    **/
+    CImgList<T>& operator=(const char *const filename) {
+      return assign(filename);
+    }
+
+    //! Create a list from the content of a display window \inplace.
+    /**
+       \param disp Display window to get content from.
+       \note <tt>list = disp;</tt> is equivalent to <tt>list.assign(disp);</tt>.
+       \see assign(const CImgDisplay&).
+    **/
+    CImgList<T>& operator=(const CImgDisplay& disp) {
+      return assign(disp);
+    }
+
+    //! Return a non-shared copy of a list.
+    /**
+        \note <tt>+list</tt> is equivalent to <tt>CImgList<T>(list,false)</tt>. It forces the copy to have non-shared elements.
+        \see CImgList(const CImgList<T>&,bool).
+    **/
+    CImgList<T> operator+() const {
+      return CImgList<T>(*this,false);
+    }
+
+    //! Return a copy of the list instance, where image \c img has been inserted at the end.
+    /**
+       \param img Image inserted at the end of the instance copy.
+       \note Define a convenient way to create temporary lists of images, as in the following code :
+       \code
+       (img1,img2,img3,img4).display("My four images");
+       \endcode
+       \see CImg<T>::operator,(const CImg<t>&).
+    **/
+    template<typename t>
+    CImgList<T>& operator,(const CImg<t>& img) {
+      return insert(img);
+    }
+
+    //! Return a copy of the list instance, where all elements of input list \c list have been inserted at the end.
+    /**
+       \param list List inserted at the end of the instance copy.
+       \see CImg<T>::operator,(const CImg<t>&).
+    **/
+    template<typename t>
+    CImgList<T>& operator,(const CImgList<t>& list) {
+      return insert(list);
+    }
+
+    //! Return image corresponding to the concatenation of all images of the instance list along specified axis.
+    /**
+      \param axis Axis used for image concatenation.
+      \note <tt>list>'x'</tt> is equivalent to <tt>list.get_append('x')</tt>.
+      \see get_append().
+    **/
+    CImg<T> operator>(const char axis) const {
+      return get_append(axis,0);
+    }
+
+    //! Return list corresponding to the splitting of all images of the instance list along specified axis.
+    /**
+      \param axis Axis used for image splitting.
+      \note <tt>list<'x'</tt> is equivalent to <tt>list.get_split('x')</tt>.
+      \see get_split().
+    **/
+    CImgList<T> operator<(const char axis) const {
+      return get_split(axis);
+    }
+
+    //@}
+    //-------------------------------------
+    //
+    //! \name Instance Characteristics
+    //@{
+    //-------------------------------------
+
+    //! Return the type of image pixel values as a C string.
+    /**
+       Return a \c char* string containing the usual type name of the image pixel values
+       (i.e. a stringified version of the template parameter \c T).
+       \note
+       - The returned string may contain spaces (as in \c "unsigned char").
+       - If the pixel type \c T does not correspond to a registered type, the string <tt>"unknown"</tt> is returned.
+       \see value_type.
+    **/
+    static const char* pixel_type() {
+      return cimg::type<T>::string();
+    }
+
+    //! Return the size of the list, i.e. the number of images contained in it.
+    /**
+      \note Similar to size() but returns result as a (signed) integer.
+      \see size().
+    **/
+    int width() const {
+      return (int)_width;
+    }
+
+    //! Return the size of the list, i.e. the number of images contained in it.
+    /**
+      \note Similar to width() but returns result as an unsigned integer.
+      \see width().
+    **/
+    unsigned int size() const {
+      return _width;
+    }
+
+    //! Return pointer to the first image of the list.
+    /**
+       \note Images in a list are stored as a buffer of \c CImg<T>.
+    **/
+    CImg<T> *data() {
+      return _data;
+    }
+
+    //! Return pointer to the first image of the list \const.
+    const CImg<T> *data() const {
+      return _data;
+    }
+
+    //! Return pointer to the pos-th image of the list.
+    /**
+       \param pos Indice of the image element to access.
+       \note <tt>list.data(n);</tt> is equivalent to <tt>list.data + n;</tt>.
+       \see data().
+    **/
+#if cimg_verbosity>=3
+    CImg<T> *data(const unsigned int pos) {
+      if (pos>=size()) {
+        cimg::warn(_cimglist_instance
+                   "data() : Invalid pointer request, at position [%u].",
+                   cimglist_instance,
+                   pos);
+        return _data;
+      }
+      return _data + pos;
+    }
+
+    const CImg<T> *data(const unsigned int l) const {
+      return const_cast<CImgList<T>*>(this)->data(l);
+    }
+#else
+    CImg<T> *data(const unsigned int l) {
+      return _data + l;
+    }
+
+    //! Return pointer to the pos-th image of the list \const.
+    const CImg<T> *data(const unsigned int l) const {
+      return _data + l;
+    }
+#endif
+
+    //! Return iterator to the first image of the list.
+    /**
+       \see end().
+    **/
+    iterator begin() {
+      return _data;
+    }
+
+    //! Return iterator to the first image of the list \const.
+    const_iterator begin() const {
+      return _data;
+    }
+
+    //! Return iterator to one position after the last image of the list.
+    /**
+       \see begin().
+    **/
+    iterator end() {
+      return _data + _width;
+    }
+
+    //! Return iterator to one position after the last image of the list \const.
+    const_iterator end() const {
+      return _data + _width;
+    }
+
+    //! Return reference to the first image of the list.
+    /**
+       \see back().
+    **/
+    CImg<T>& front() {
+      return *_data;
+    }
+
+    //! Return reference to the first image of the list \const.
+    const CImg<T>& front() const {
+      return *_data;
+    }
+
+    //! Return a reference to the last image of the list.
+    /**
+       \see front().
+    **/
+    const CImg<T>& back() const {
+      return *(_data + _width - 1);
+    }
+
+    //! Return a reference to the last image of the list \const.
+    CImg<T>& back() {
+      return *(_data + _width - 1);
+    }
+
+    //! Return pos-th image of the list.
+    /**
+       \param pos Indice of the image element to access.
+    **/
+    CImg<T>& at(const int pos) {
+      if (is_empty())
+        throw CImgInstanceException(_cimglist_instance
+                                    "at() : Empty instance.",
+                                    cimglist_instance);
+
+      return _data[pos<0?0:pos>=(int)_width?(int)_width-1:pos];
+    }
+
+    //! Access to pixel value with Dirichlet boundary conditions.
+    /**
+       \param pos Indice of the image element to access.
+       \param x X-coordinate of the pixel value.
+       \param y Y-coordinate of the pixel value.
+       \param z Z-coordinate of the pixel value.
+       \param c C-coordinate of the pixel value.
+       \param out_value Default value returned if \c offset is outside image bounds.
+       \note <tt>list.atNXYZC(p,x,y,z,c);</tt> is equivalent to <tt>list[p].atXYZC(x,y,z,c);</tt>.
+       \see CImg<T>::atXYZC().
+    **/
+    T& atNXYZC(const int pos, const int x, const int y, const int z, const int c, const T out_value) {
+      return (pos<0 || pos>=(int)_width)?(cimg::temporary(out_value)=out_value):_data[pos].atXYZC(x,y,z,c,out_value);
+    }
+
+    //! Access to pixel value with Dirichlet boundary conditions \const.
+    T atNXYZC(const int pos, const int x, const int y, const int z, const int c, const T out_value) const {
+      return (pos<0 || pos>=(int)_width)?out_value:_data[pos].atXYZC(x,y,z,c,out_value);
+    }
+
+    //! Access to pixel value with Neumann boundary conditions.
+    /**
+       \param pos Indice of the image element to access.
+       \param x X-coordinate of the pixel value.
+       \param y Y-coordinate of the pixel value.
+       \param z Z-coordinate of the pixel value.
+       \param c C-coordinate of the pixel value.
+       \note <tt>list.atNXYZC(p,x,y,z,c);</tt> is equivalent to <tt>list[p].atXYZC(x,y,z,c);</tt>.
+       \see CImg<T>::atXYZC().
+    **/
+    T& atNXYZC(const int pos, const int x, const int y, const int z, const int c) {
+      if (is_empty())
+        throw CImgInstanceException(_cimglist_instance
+                                    "atNXYZC() : Empty instance.",
+                                    cimglist_instance);
+
+      return _atNXYZC(pos,x,y,z,c);
+    }
+
+    //! Access to pixel value with Neumann boundary conditions \const.
+    T atNXYZC(const int pos, const int x, const int y, const int z, const int c) const {
+      if (is_empty())
+        throw CImgInstanceException(_cimglist_instance
+                                    "atNXYZC() : Empty instance.",
+                                    cimglist_instance);
+
+      return _atNXYZC(pos,x,y,z,c);
+    }
+
+    T& _atNXYZC(const int pos, const int x, const int y, const int z, const int c) {
+      return _data[pos<0?0:(pos>=(int)_width?(int)_width-1:pos)].atXYZC(x,y,z,c);
+    }
+
+    T _atNXYZC(const int pos, const int x, const int y, const int z, const int c) const {
+      return _data[pos<0?0:(pos>=(int)_width?(int)_width-1:pos)].atXYZC(x,y,z,c);
+    }
+
+    //! Access to pixel value with Dirichlet boundary conditions for the three first coordinates (\c pos, \c x,\c y,\c z).
+    /**
+       \param pos Indice of the image element to access.
+       \param x X-coordinate of the pixel value.
+       \param y Y-coordinate of the pixel value.
+       \param z Z-coordinate of the pixel value.
+       \param c C-coordinate of the pixel value.
+       \param out_value Default value returned if \c offset is outside image bounds.
+       \note <tt>list.atNXYZ(p,x,y,z,c);</tt> is equivalent to <tt>list[p].atXYZ(x,y,z,c);</tt>.
+       \see CImg<T>::atXYZ().
+    **/
+    T& atNXYZ(const int pos, const int x, const int y, const int z, const int c, const T out_value) {
+      return (pos<0 || pos>=(int)_width)?(cimg::temporary(out_value)=out_value):_data[pos].atXYZ(x,y,z,c,out_value);
+    }
+
+    //! Access to pixel value with Dirichlet boundary conditions for the three first coordinates (\c pos, \c x,\c y,\c z) \const.
+    T atNXYZ(const int pos, const int x, const int y, const int z, const int c, const T out_value) const {
+      return (pos<0 || pos>=(int)_width)?out_value:_data[pos].atXYZ(x,y,z,c,out_value);
+    }
+
+    //! Access to pixel value with Neumann boundary conditions for the four first coordinates (\c pos, \c x,\c y,\c z).
+    /**
+       \param pos Indice of the image element to access.
+       \param x X-coordinate of the pixel value.
+       \param y Y-coordinate of the pixel value.
+       \param z Z-coordinate of the pixel value.
+       \param c C-coordinate of the pixel value.
+       \note <tt>list.atNXYZ(p,x,y,z,c);</tt> is equivalent to <tt>list[p].atXYZ(x,y,z,c);</tt>.
+       \see CImg<T>::atXYZ().
+    **/
+   T& atNXYZ(const int pos, const int x, const int y, const int z, const int c=0) {
+      if (is_empty())
+        throw CImgInstanceException(_cimglist_instance
+                                    "atNXYZ() : Empty instance.",
+                                    cimglist_instance);
+
+      return _atNXYZ(pos,x,y,z,c);
+    }
+
+    //! Access to pixel value with Neumann boundary conditions for the four first coordinates (\c pos, \c x,\c y,\c z) \const.
+    T atNXYZ(const int pos, const int x, const int y, const int z, const int c=0) const {
+      if (is_empty())
+        throw CImgInstanceException(_cimglist_instance
+                                    "atNXYZ() : Empty instance.",
+                                    cimglist_instance);
+
+      return _atNXYZ(pos,x,y,z,c);
+    }
+
+    T& _atNXYZ(const int pos, const int x, const int y, const int z, const int c=0) {
+      return _data[pos<0?0:(pos>=(int)_width?(int)_width-1:pos)].atXYZ(x,y,z,c);
+    }
+
+    T _atNXYZ(const int pos, const int x, const int y, const int z, const int c=0) const {
+      return _data[pos<0?0:(pos>=(int)_width?(int)_width-1:pos)].atXYZ(x,y,z,c);
+    }
+
+    //! Access to pixel value with Dirichlet boundary conditions for the three first coordinates (\c pos, \c x,\c y).
+    /**
+       \param pos Indice of the image element to access.
+       \param x X-coordinate of the pixel value.
+       \param y Y-coordinate of the pixel value.
+       \param z Z-coordinate of the pixel value.
+       \param c C-coordinate of the pixel value.
+       \param out_value Default value returned if \c offset is outside image bounds.
+       \note <tt>list.atNXYZ(p,x,y,z,c);</tt> is equivalent to <tt>list[p].atXYZ(x,y,z,c);</tt>.
+       \see CImg<T>::atXYZ().
+    **/
+    T& atNXY(const int pos, const int x, const int y, const int z, const int c, const T out_value) {
+      return (pos<0 || pos>=(int)_width)?(cimg::temporary(out_value)=out_value):_data[pos].atXY(x,y,z,c,out_value);
+    }
+
+    //! Access to pixel value with Dirichlet boundary conditions for the three first coordinates (\c pos, \c x,\c y) \const.
+    T atNXY(const int pos, const int x, const int y, const int z, const int c, const T out_value) const {
+      return (pos<0 || pos>=(int)_width)?out_value:_data[pos].atXY(x,y,z,c,out_value);
+    }
+
+    //! Access to pixel value with Neumann boundary conditions for the three first coordinates (\c pos, \c x,\c y).
+    /**
+       \param pos Indice of the image element to access.
+       \param x X-coordinate of the pixel value.
+       \param y Y-coordinate of the pixel value.
+       \param z Z-coordinate of the pixel value.
+       \param c C-coordinate of the pixel value.
+       \param out_value Default value returned if \c offset is outside image bounds.
+       \note <tt>list.atNXYZ(p,x,y,z,c);</tt> is equivalent to <tt>list[p].atXYZ(x,y,z,c);</tt>.
+       \see CImg<T>::atXYZ().
+    **/
+    T& atNXY(const int pos, const int x, const int y, const int z=0, const int c=0) {
+      if (is_empty())
+        throw CImgInstanceException(_cimglist_instance
+                                    "atNXY() : Empty instance.",
+                                    cimglist_instance);
+
+      return _atNXY(pos,x,y,z,c);
+    }
+
+    //! Access to pixel value with Neumann boundary conditions for the three first coordinates (\c pos, \c x,\c y) \const.
+    T atNXY(const int pos, const int x, const int y, const int z=0, const int c=0) const {
+      if (is_empty())
+        throw CImgInstanceException(_cimglist_instance
+                                    "atNXY() : Empty instance.",
+                                    cimglist_instance);
+
+      return _atNXY(pos,x,y,z,c);
+    }
+
+    T& _atNXY(const int pos, const int x, const int y, const int z=0, const int c=0) {
+      return _data[pos<0?0:(pos>=(int)_width?(int)_width-1:pos)].atXY(x,y,z,c);
+    }
+
+    T _atNXY(const int pos, const int x, const int y, const int z=0, const int c=0) const {
+      return _data[pos<0?0:(pos>=(int)_width?(int)_width-1:pos)].atXY(x,y,z,c);
+    }
+
+    //! Access to pixel value with Dirichlet boundary conditions for the two first coordinates (\c pos,\c x).
+    /**
+       \param pos Indice of the image element to access.
+       \param x X-coordinate of the pixel value.
+       \param y Y-coordinate of the pixel value.
+       \param z Z-coordinate of the pixel value.
+       \param c C-coordinate of the pixel value.
+       \param out_value Default value returned if \c offset is outside image bounds.
+       \note <tt>list.atNXYZ(p,x,y,z,c);</tt> is equivalent to <tt>list[p].atXYZ(x,y,z,c);</tt>.
+       \see CImg<T>::atXYZ().
+    **/
+    T& atNX(const int pos, const int x, const int y, const int z, const int c, const T out_value) {
+      return (pos<0 || pos>=(int)_width)?(cimg::temporary(out_value)=out_value):_data[pos].atX(x,y,z,c,out_value);
+    }
+
+    //! Access to pixel value with Dirichlet boundary conditions for the two first coordinates (\c pos,\c x) \const.
+    T atNX(const int pos, const int x, const int y, const int z, const int c, const T out_value) const {
+      return (pos<0 || pos>=(int)_width)?out_value:_data[pos].atX(x,y,z,c,out_value);
+    }
+
+    //! Access to pixel value with Neumann boundary conditions for the two first coordinates (\c pos, \c x).
+    /**
+       \param pos Indice of the image element to access.
+       \param x X-coordinate of the pixel value.
+       \param y Y-coordinate of the pixel value.
+       \param z Z-coordinate of the pixel value.
+       \param c C-coordinate of the pixel value.
+       \note <tt>list.atNXYZ(p,x,y,z,c);</tt> is equivalent to <tt>list[p].atXYZ(x,y,z,c);</tt>.
+       \see CImg<T>::atXYZ().
+    **/
+    T& atNX(const int pos, const int x, const int y=0, const int z=0, const int c=0) {
+      if (is_empty())
+        throw CImgInstanceException(_cimglist_instance
+                                    "atNX() : Empty instance.",
+                                    cimglist_instance);
+
+      return _atNX(pos,x,y,z,c);
+    }
+
+    //! Access to pixel value with Neumann boundary conditions for the two first coordinates (\c pos, \c x) \const.
+    T atNX(const int pos, const int x, const int y=0, const int z=0, const int c=0) const {
+      if (is_empty())
+        throw CImgInstanceException(_cimglist_instance
+                                    "atNX() : Empty instance.",
+                                    cimglist_instance);
+
+      return _atNX(pos,x,y,z,c);
+    }
+
+    T& _atNX(const int pos, const int x, const int y=0, const int z=0, const int c=0) {
+      return _data[pos<0?0:(pos>=(int)_width?(int)_width-1:pos)].atX(x,y,z,c);
+    }
+
+    T _atNX(const int pos, const int x, const int y=0, const int z=0, const int c=0) const {
+      return _data[pos<0?0:(pos>=(int)_width?(int)_width-1:pos)].atX(x,y,z,c);
+    }
+
+    //! Access to pixel value with Dirichlet boundary conditions for the first coordinates (\c pos).
+    /**
+       \param pos Indice of the image element to access.
+       \param x X-coordinate of the pixel value.
+       \param y Y-coordinate of the pixel value.
+       \param z Z-coordinate of the pixel value.
+       \param c C-coordinate of the pixel value.
+       \param out_value Default value returned if \c offset is outside image bounds.
+       \note <tt>list.atNXYZ(p,x,y,z,c);</tt> is equivalent to <tt>list[p].atXYZ(x,y,z,c);</tt>.
+       \see CImg<T>::atXYZ().
+    **/
+    T& atN(const int pos, const int x, const int y, const int z, const int c, const T out_value) {
+      return (pos<0 || pos>=(int)_width)?(cimg::temporary(out_value)=out_value):(*this)(pos,x,y,z,c);
+    }
+
+    //! Access to pixel value with Dirichlet boundary conditions for the first coordinates (\c pos) \const.
+    T atN(const int pos, const int x, const int y, const int z, const int c, const T out_value) const {
+      return (pos<0 || pos>=(int)_width)?out_value:(*this)(pos,x,y,z,c);
+    }
+
+    //! Return pixel value with Neumann boundary conditions for the first coordinates (\c pos).
+    /**
+       \param pos Indice of the image element to access.
+       \param x X-coordinate of the pixel value.
+       \param y Y-coordinate of the pixel value.
+       \param z Z-coordinate of the pixel value.
+       \param c C-coordinate of the pixel value.
+       \note <tt>list.atNXYZ(p,x,y,z,c);</tt> is equivalent to <tt>list[p].atXYZ(x,y,z,c);</tt>.
+       \see CImg<T>::atXYZ().
+    **/
+    T& atN(const int pos, const int x=0, const int y=0, const int z=0, const int c=0) {
+      if (is_empty())
+        throw CImgInstanceException(_cimglist_instance
+                                    "atN() : Empty instance.",
+                                    cimglist_instance);
+      return _atN(pos,x,y,z,c);
+    }
+
+    //! Return pixel value with Neumann boundary conditions for the first coordinates (\c pos) \const.
+    T atN(const int pos, const int x=0, const int y=0, const int z=0, const int c=0) const {
+      if (is_empty())
+        throw CImgInstanceException(_cimglist_instance
+                                    "atN() : Empty instance.",
+                                    cimglist_instance);
+      return _atN(pos,x,y,z,c);
+    }
+
+    T& _atN(const int pos, const int x=0, const int y=0, const int z=0, const int c=0) {
+      return _data[pos<0?0:(pos>=(int)_width?(int)_width-1:pos)](x,y,z,c);
+    }
+
+    T _atN(const int pos, const int x=0, const int y=0, const int z=0, const int c=0) const {
+      return _data[pos<0?0:(pos>=(int)_width?(int)_width-1:pos)](x,y,z,c);
+    }
+
+    //! Return a C-string containing the values of all images in the instance list.
+    /**
+       \param separator Character separator set between consecutive pixel values.
+       \param max_size Maximum size of the returned string.
+       \note The result is returne as a <tt>CImg<char></tt> image whose pixel buffer contains the desired C-string.
+    **/
+    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<_width-1; ++l) {
+        CImg<charT> item = _data[l].value_string(separator,0);
+        item.back() = separator;
+        item.move_to(items);
+      }
+      _data[_width-1].value_string(separator,0).move_to(items);
+      CImg<charT> res; (items>'x').move_to(res);
+      if (max_size) { res.crop(0,max_size); res(max_size) = 0; }
+      return res;
+    }
+
+    //@}
+    //-------------------------------------
+    //
+    //! \name Instance Checking
+    //@{
+    //-------------------------------------
+
+    //! Return \c true if list is empty.
+    /**
+       \see operator bool().
+    **/
+    bool is_empty() const {
+      return (!_data || !_width);
+    }
+
+    //! Return \c true if list has \c n images.
+    bool is_sameN(const unsigned int size_n) const {
+      return _width==size_n;
+    }
+
+    //! Return \c true if list has \c n images.
+    template<typename t>
+    bool is_sameN(const CImgList<t>& list) const {
+      return is_sameN(list._width);
+    }
+
+    // Define useful functions to check list dimensions.
+    // (cannot be documented because 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<_width && 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<_width && 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<_width && 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<_width && 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(_width,list._width); \
+      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(XC)
+    _cimglist_def_is_same(YZ)
+    _cimglist_def_is_same(YC)
+    _cimglist_def_is_same(XYZ)
+    _cimglist_def_is_same(XYC)
+    _cimglist_def_is_same(YZC)
+    _cimglist_def_is_same(XYZC)
+    _cimglist_def_is_same1(X)
+    _cimglist_def_is_same1(Y)
+    _cimglist_def_is_same1(Z)
+    _cimglist_def_is_same1(C)
+    _cimglist_def_is_same2(X,Y)
+    _cimglist_def_is_same2(X,Z)
+    _cimglist_def_is_same2(X,C)
+    _cimglist_def_is_same2(Y,Z)
+    _cimglist_def_is_same2(Y,C)
+    _cimglist_def_is_same2(Z,C)
+    _cimglist_def_is_same3(X,Y,Z)
+    _cimglist_def_is_same3(X,Y,C)
+    _cimglist_def_is_same3(X,Z,C)
+    _cimglist_def_is_same3(Y,Z,C)
+
+    //! Return \c true if dimensions of each image of the list match specified arguments.
+    /**
+      \param dx Checked image width.
+      \param dy Checked image height.
+      \param dz Checked image depth.
+      \param dc Checked image spectrum.
+      \see is_sameNXYZC().
+    **/
+    bool is_sameXYZC(const unsigned int dx, const unsigned int dy, const unsigned int dz, const unsigned int dc) const {
+      bool res = true;
+      for (unsigned int l = 0; l<_width && res; ++l) res = _data[l].is_sameXYZC(dx,dy,dz,dc);
+      return res;
+    }
+
+    //! Return \c true if list dimensions match specified arguments.
+    /**
+       \param n Number of images in the list.
+       \param dx Checked image width.
+       \param dy Checked image height.
+       \param dz Checked image depth.
+       \param dc Checked image spectrum.
+       \see is_sameXYZC().
+    **/
+    bool is_sameNXYZC(const unsigned int n, const unsigned int dx, const unsigned int dy, const unsigned int dz, const unsigned int dc) const {
+      return is_sameN(n) && is_sameXYZC(dx,dy,dz,dc);
+    }
+
+    //! Return \c true if list contains pixel located at (\c n,\c x,\c y,\c z,\c c).
+    /**
+       \param n Index of the image whom checked pixel value belong to.
+       \param x X-coordinate of the checked pixel value.
+       \param y Y-coordinate of the checked pixel value.
+       \param z Z-coordinate of the checked pixel value.
+       \param c C-coordinate of the checked pixel value.
+       \see contains(), CImg<T>::containsXYZC().
+    **/
+    bool containsNXYZC(const int n, const int x=0, const int y=0, const int z=0, const int c=0) const {
+      if (is_empty()) return false;
+      return n>=0 && n<(int)_width && x>=0 && x<_data[n].width() && y>=0 && y<_data[n].height() &&
+        z>=0 && z<_data[n].depth() && c>=0 && c<_data[n].spectrum();
+    }
+
+    //! Return \c true if list contains image with index [\c n].
+    /**
+       \param n Index of the checked image.
+       \see contains().
+    **/
+    bool containsN(const int n) const {
+      if (is_empty()) return false;
+      return n>=0 && n<(int)_width;
+    }
+
+    //! Return \c true if one image of the list contains the specified referenced value.
+    /**
+       \param pixel Reference to pixel value to test.
+       \param[out] x Index of image containing the pixel value, if test succeeds.
+       \param[out] x X-coordinate of the pixel value, if test succeeds.
+       \param[out] y Y-coordinate of the pixel value, if test succeeds.
+       \param[out] z Z-coordinate of the pixel value, if test succeeds.
+       \param[out] c C-coordinate of the pixel value, if test succeeds.
+       \note If true, set coordinates (n,x,y,z,c).
+    **/
+    template<typename t>
+    bool contains(const T& pixel, t& n, t& x, t&y, t& z, t& c) const {
+      if (is_empty()) return false;
+      cimglist_for(*this,l) if (_data[l].contains(pixel,x,y,z,c)) { n = (t)l; return true; }
+      return false;
+    }
+
+    //! Return \c true if one of the image list contains the specified referenced value.
+    /**
+       \param pixel Reference to pixel value to test.
+       \param[out] x Index of image containing the pixel value, if test succeeds.
+       \param[out] x X-coordinate of the pixel value, if test succeeds.
+       \param[out] y Y-coordinate of the pixel value, if test succeeds.
+       \param[out] z Z-coordinate of the pixel value, if test succeeds.
+       \note 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 c;
+      return contains(pixel,n,x,y,z,c);
+    }
+
+    //! Return \c true if one of the image list contains the specified referenced value.
+    /**
+       \param pixel Reference to pixel value to test.
+       \param[out] x Index of image containing the pixel value, if test succeeds.
+       \param[out] x X-coordinate of the pixel value, if test succeeds.
+       \param[out] y Y-coordinate of the pixel value, if test succeeds.
+       \note If true, set coordinates (n,x,y).
+    **/
+    template<typename t>
+    bool contains(const T& pixel, t& n, t& x, t&y) const {
+      t z, c;
+      return contains(pixel,n,x,y,z,c);
+    }
+
+    //! Return \c true if one of the image list contains the specified referenced value.
+    /**
+       \param pixel Reference to pixel value to test.
+       \param[out] x Index of image containing the pixel value, if test succeeds.
+       \param[out] x X-coordinate of the pixel value, if test succeeds.
+       \note If true, set coordinates (n,x).
+    **/
+    template<typename t>
+    bool contains(const T& pixel, t& n, t& x) const {
+      t y, z, c;
+      return contains(pixel,n,x,y,z,c);
+    }
+
+    //! Return \c true if one of the image list contains the specified referenced value.
+    /**
+       \param pixel Reference to pixel value to test.
+       \param[out] x Index of image containing the pixel value, if test succeeds.
+       \note If true, set coordinates (n).
+    **/
+    template<typename t>
+    bool contains(const T& pixel, t& n) const {
+      t x, y, z, c;
+      return contains(pixel,n,x,y,z,c);
+    }
+
+    //! Return \c true if one of the image list contains the specified referenced value.
+    /**
+       \param pixel Reference to pixel value to test.
+    **/
+    bool contains(const T& pixel) const {
+      unsigned int n, x, y, z, c;
+      return contains(pixel,n,x,y,z,c);
+    }
+
+    //! Return \c true if the list contains the image 'img'.
+    /**
+       \param img Reference to image to test.
+       \param[out] n Index of image in the list, if test succeeds.
+       \note 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.
+    /**
+       \param img Reference to image to test.
+    **/
+    bool contains(const CImg<T>& img) const {
+      unsigned int n;
+      return contains(img,n);
+    }
+
+    //@}
+    //-------------------------------------
+    //
+    //! \name Mathematical Functions
+    //@{
+    //-------------------------------------
+
+    //! Return a reference to the minimum pixel value of the instance list.
+    /**
+       \see max().
+    **/
+    T& min() {
+      if (is_empty())
+        throw CImgInstanceException(_cimglist_instance
+                                    "min() : Empty instance.",
+                                    cimglist_instance);
+      T *ptr_min = _data->_data;
+      T min_value = *ptr_min;
+      cimglist_for(*this,l) {
+        const CImg<T>& img = _data[l];
+        cimg_for(img,ptrs,T) if (*ptrs<min_value) min_value = *(ptr_min=ptrs);
+      }
+      return *ptr_min;
+    }
+
+    //! Return a reference to the minimum pixel value of the instance list \const.
+    const T& min() const {
+      if (is_empty())
+        throw CImgInstanceException(_cimglist_instance
+                                    "min() : Empty instance.",
+                                    cimglist_instance);
+      const T *ptr_min = _data->_data;
+      T min_value = *ptr_min;
+      cimglist_for(*this,l) {
+        const CImg<T>& img = _data[l];
+        cimg_for(img,ptrs,T) if (*ptrs<min_value) min_value = *(ptr_min=ptrs);
+      }
+      return *ptr_min;
+    }
+
+    //! Return a reference to the maximum pixel value of the instance list.
+    /**
+       \see min().
+    **/
+    T& max() {
+      if (is_empty())
+        throw CImgInstanceException(_cimglist_instance
+                                    "max() : Empty instance.",
+                                    cimglist_instance);
+      T *ptr_max = _data->_data;
+      T max_value = *ptr_max;
+      cimglist_for(*this,l) {
+        const CImg<T>& img = _data[l];
+        cimg_for(img,ptrs,T) if (*ptrs>max_value) max_value = *(ptr_max=ptrs);
+      }
+      return *ptr_max;
+    }
+
+    //! Return a reference to the maximum pixel value of the instance list \const.
+    const T& max() const {
+      if (is_empty())
+        throw CImgInstanceException(_cimglist_instance
+                                    "max() : Empty instance.",
+                                    cimglist_instance);
+      const T *ptr_max = _data->_data;
+      T max_value = *ptr_max;
+      cimglist_for(*this,l) {
+        const CImg<T>& img = _data[l];
+        cimg_for(img,ptrs,T) if (*ptrs>max_value) max_value = *(ptr_max=ptrs);
+      }
+      return *ptr_max;
+    }
+
+    //! Return a reference to the minimum pixel value of the instance list and return the maximum vvalue as well.
+    /**
+       \param[out] max_val Value of the maximum value found.
+       \see max_min().
+    **/
+    template<typename t>
+    T& min_max(t& max_val) {
+      if (is_empty())
+        throw CImgInstanceException(_cimglist_instance
+                                    "min_max() : Empty instance.",
+                                    cimglist_instance);
+      T *ptr_min = _data->_data;
+      T min_value = *ptr_min, max_value = min_value;
+      cimglist_for(*this,l) {
+        const CImg<T>& img = _data[l];
+        cimg_for(img,ptrs,T) {
+          const T val = *ptrs;
+          if (val<min_value) { min_value = val; ptr_min = ptrs; }
+          if (val>max_value) max_value = val;
+        }
+      }
+      max_val = (t)max_value;
+      return *ptr_min;
+    }
+
+    //! Return a reference to the minimum pixel value of the instance list and return the maximum vvalue as well \const.
+    /**
+       \param[out] max_val Value of the maximum value found.
+       \see max_min().
+    **/
+    template<typename t>
+    const T& min_max(t& max_val) const {
+      if (is_empty())
+        throw CImgInstanceException(_cimglist_instance
+                                    "min_max() : Empty instance.",
+                                    cimglist_instance);
+      const T *ptr_min = _data->_data;
+      T min_value = *ptr_min, max_value = min_value;
+      cimglist_for(*this,l) {
+        const CImg<T>& img = _data[l];
+        cimg_for(img,ptrs,T) {
+          const T val = *ptrs;
+          if (val<min_value) { min_value = val; ptr_min = ptrs; }
+          if (val>max_value) max_value = val;
+        }
+      }
+      max_val = (t)max_value;
+      return *ptr_min;
+    }
+
+    //! Return a reference to the minimum pixel value of the instance list and return the minimum value as well.
+    /**
+       \param[out] min_val Value of the minimum value found.
+       \see min_max().
+    **/
+    template<typename t>
+    T& max_min(t& min_val) {
+      if (is_empty())
+        throw CImgInstanceException(_cimglist_instance
+                                    "max_min() : Empty instance.",
+                                    cimglist_instance);
+      T *ptr_max = _data->_data;
+      T min_value = *ptr_max, max_value = min_value;
+      cimglist_for(*this,l) {
+        const CImg<T>& img = _data[l];
+        cimg_for(img,ptrs,T) {
+          const T val = *ptrs;
+          if (val>max_value) { max_value = val; ptr_max = ptrs; }
+          if (val<min_value) min_value = val;
+        }
+      }
+      min_val = (t)min_value;
+      return *ptr_max;
+    }
+
+    //! Return a reference to the minimum pixel value of the instance list and return the minimum value as well \const.
+    template<typename t>
+    const T& max_min(t& min_val) const {
+      if (is_empty())
+        throw CImgInstanceException(_cimglist_instance
+                                    "max_min() : Empty instance.",
+                                    cimglist_instance);
+      const T *ptr_max = _data->_data;
+      T min_value = *ptr_max, max_value = min_value;
+      cimglist_for(*this,l) {
+        const CImg<T>& img = _data[l];
+        cimg_for(img,ptrs,T) {
+          const T val = *ptrs;
+          if (val>max_value) { max_value = val; ptr_max = ptrs; }
+          if (val<min_value) min_value = val;
+        }
+      }
+      min_val = (t)min_value;
+      return *ptr_max;
+    }
+
+    //@}
+    //---------------------------
+    //
+    //! \name List Manipulation
+    //@{
+    //---------------------------
+
+    //! Insert a copy of the image \c img into the current image list, at position \c pos.
+    /**
+        \param img Image to insert a copy to the list.
+        \param pos Index of the insertion.
+        \param is_shared Flag telling if the inserted image is a shared copy of \c img or not.
+    **/
+    template<typename t>
+    CImgList<T>& insert(const CImg<t>& img, const unsigned int pos=~0U, const bool is_shared=false) {
+      const unsigned int npos = pos==~0U?_width:pos;
+      if (npos>_width)
+        throw CImgArgumentException(_cimglist_instance
+                                    "insert() : Invalid insertion request of specified image (%u,%u,%u,%u,%p) at position %u.",
+                                    cimglist_instance,
+                                    img._width,img._height,img._depth,img._spectrum,img._data,npos);
+      if (is_shared)
+        throw CImgArgumentException(_cimglist_instance
+                                    "insert() : Invalid insertion request of specified shared image CImg<%s>(%u,%u,%u,%u,%p) at position %u "
+                                    "(pixel types are different).",
+                                    cimglist_instance,
+                                    img.pixel_type(),img._width,img._height,img._depth,img._spectrum,img._data,npos);
+
+      CImg<T> *const new_data = (++_width>_allocated_width)?new CImg<T>[_allocated_width?(_allocated_width<<=1):(_allocated_width=16)]:0;
+      if (!_data) { // Insert new element into empty list.
+        _data = new_data;
+        *_data = img;
+      } else {
+        if (new_data) { // Insert with re-allocation.
+          if (npos) std::memcpy(new_data,_data,sizeof(CImg<T>)*npos);
+          if (npos!=_width-1) std::memcpy(new_data+npos+1,_data+npos,sizeof(CImg<T>)*(_width-1-npos));
+          std::memset(_data,0,sizeof(CImg<T>)*(_width-1));
+          delete[] _data;
+          _data = new_data;
+        } else if (npos!=_width-1) std::memmove(_data+npos+1,_data+npos,sizeof(CImg<T>)*(_width-1-npos)); // Insert without re-allocation.
+        _data[npos]._width = _data[npos]._height = _data[npos]._depth = _data[npos]._spectrum = 0; _data[npos]._data = 0;
+        _data[npos] = img;
+      }
+      return *this;
+    }
+
+    //! Insert a copy of the image \c img into the current image list, at position \c pos \specialization.
+    CImgList<T>& insert(const CImg<T>& img, const unsigned int pos=~0U, const bool is_shared=false) {
+      const unsigned int npos = pos==~0U?_width:pos;
+      if (npos>_width)
+        throw CImgArgumentException(_cimglist_instance
+                                    "insert() : Invalid insertion request of specified image (%u,%u,%u,%u,%p) at position %u.",
+                                    cimglist_instance,
+                                    img._width,img._height,img._depth,img._spectrum,img._data,npos);
+      CImg<T> *const new_data = (++_width>_allocated_width)?new CImg<T>[_allocated_width?(_allocated_width<<=1):(_allocated_width=16)]:0;
+      if (!_data) { // Insert new element into empty list.
+        _data = new_data;
+        if (is_shared && img) {
+          _data->_width = img._width; _data->_height = img._height; _data->_depth = img._depth; _data->_spectrum = img._spectrum;
+          _data->_is_shared = true; _data->_data = img._data;
+        } else *_data = img;
+      }
+      else {
+        if (new_data) { // Insert with re-allocation.
+          if (npos) std::memcpy(new_data,_data,sizeof(CImg<T>)*npos);
+          if (npos!=_width-1) std::memcpy(new_data+npos+1,_data+npos,sizeof(CImg<T>)*(_width-1-npos));
+          if (is_shared && img) {
+            new_data[npos]._width = img._width; new_data[npos]._height = img._height; new_data[npos]._depth = img._depth;
+            new_data[npos]._spectrum = img._spectrum; 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]._spectrum = 0; new_data[npos]._data = 0;
+            new_data[npos] = img;
+          }
+          std::memset(_data,0,sizeof(CImg<T>)*(_width-1));
+          delete[] _data;
+          _data = new_data;
+        } else { // Insert without re-allocation.
+          if (npos!=_width-1) std::memmove(_data+npos+1,_data+npos,sizeof(CImg<T>)*(_width-1-npos));
+          if (is_shared && img) {
+            _data[npos]._width = img._width; _data[npos]._height = img._height; _data[npos]._depth = img._depth; _data[npos]._spectrum = img._spectrum;
+            _data[npos]._is_shared = true; _data[npos]._data = img._data;
+          } else {
+            _data[npos]._width = _data[npos]._height = _data[npos]._depth = _data[npos]._spectrum = 0; _data[npos]._data = 0;
+            _data[npos] = img;
+          }
+        }
+      }
+      return *this;
+    }
+
+    //! Insert a copy of the image \c img into the current image list, at position \c pos \newinstance.
+    template<typename t>
+    CImgList<T> get_insert(const CImg<t>& img, const unsigned int pos=~0U, const bool is_shared=false) const {
+      return (+*this).insert(img,pos,is_shared);
+    }
+
+    //! Insert n empty images img into the current image list, at position \p pos.
+    /**
+       \param n Number of empty images to insert.
+       \param pos Index of the insertion.
+    **/
+    CImgList<T>& insert(const unsigned int n, const unsigned int pos=~0U) {
+      CImg<T> empty;
+      if (!n) return *this;
+      const unsigned int npos = pos==~0U?_width:pos;
+      for (unsigned int i = 0; i<n; ++i) insert(empty,npos+i);
+      return *this;
+    }
+
+    //! Insert n empty images img into the current image list, at position \p pos \newinstance.
+    CImgList<T> get_insert(const unsigned int n, const unsigned int pos=~0U) const {
+      return (+*this).insert(n,pos);
+    }
+
+    //! Insert \c n copies of the image \c img into the current image list, at position \c pos.
+    /**
+       \param n Number of image copies to insert.
+       \param img Image to insert by copy.
+       \param pos Index of the insertion.
+       \param is_shared Flag telling if inserted images are shared copies of \c img or not.
+    **/
+    template<typename t>
+    CImgList<T>& insert(const unsigned int n, const CImg<t>& img, const unsigned int pos=~0U, const bool is_shared=false) {
+      if (!n) return *this;
+      const unsigned int npos = pos==~0U?_width:pos;
+      insert(img,npos,is_shared);
+      for (unsigned int i = 1; i<n; ++i) insert(_data[npos],npos+i,is_shared);
+      return *this;
+    }
+
+    //! Insert \c n copies of the image \c img into the current image list, at position \c pos \newinstance.
+    template<typename t>
+    CImgList<T> get_insert(const unsigned int n, const CImg<t>& img, const unsigned int pos=~0U, const bool is_shared=false) const {
+      return (+*this).insert(n,img,pos,is_shared);
+    }
+
+    //! Insert a copy of the image list \c list into the current image list, starting from position \c pos.
+    /**
+      \param list Image list to insert.
+      \param pos Index of the insertion.
+      \param is_shared Flag telling if inserted images are shared copies of images of \c list or not.
+    **/
+    template<typename t>
+    CImgList<T>& insert(const CImgList<t>& list, const unsigned int pos=~0U, const bool is_shared=false) {
+      const unsigned int npos = pos==~0U?_width:pos;
+      if ((void*)this!=(void*)&list) cimglist_for(list,l) insert(list[l],npos+l,is_shared);
+      else insert(CImgList<T>(list),npos,is_shared);
+      return *this;
+    }
+
+    //! Insert a copy of the image list \c list into the current image list, starting from position \c pos \newinstance.
+    template<typename t>
+    CImgList<T> get_insert(const CImgList<t>& list, const unsigned int pos=~0U, const bool is_shared=false) const {
+      return (+*this).insert(list,pos,is_shared);
+    }
+
+    //! Insert n copies of the list \c list at position \c pos of the current list.
+    /**
+      \param n Number of list copies to insert.
+      \param list Image list to insert.
+      \param pos Index of the insertion.
+      \param is_shared Flag telling if inserted images are shared copies of images of \c list or not.
+    **/
+    template<typename t>
+    CImgList<T>& insert(const unsigned int n, const CImgList<t>& list, const unsigned int pos=~0U, const bool is_shared=false) {
+      if (!n) return *this;
+      const unsigned int npos = pos==~0U?_width:pos;
+      for (unsigned int i = 0; i<n; ++i) insert(list,npos,is_shared);
+      return *this;
+    }
+
+    //! Insert n copies of the list \c list at position \c pos of the current list \newinstance.
+    template<typename t>
+    CImgList<T> get_insert(const unsigned int n, const CImgList<t>& list, const unsigned int pos=~0U, const bool is_shared=false) const {
+      return (+*this).insert(n,list,pos,is_shared);
+    }
+
+    //! Remove all images between from indexes.
+    /**
+      \param pos1 Starting index of the removal.
+      \param pos2 Ending index of the removal.
+    **/
+    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<_width?tpos2:_width-1;
+      if (npos1>=_width)
+        throw CImgArgumentException(_cimglist_instance
+                                    "remove() : Invalid remove request at positions %u->%u.",
+                                    cimglist_instance,
+                                    npos1,tpos2);
+      else {
+        if (tpos2>=_width)
+          throw CImgArgumentException(_cimglist_instance
+                                      "remove() : Invalid remove request at positions %u->%u.",
+                                      cimglist_instance,
+                                      npos1,tpos2);
+
+        for (unsigned int k = npos1; k<=npos2; ++k) _data[k].assign();
+        const unsigned int nb = 1 + npos2 - npos1;
+        if (!(_width-=nb)) return assign();
+        if (_width>(_allocated_width>>2) || _allocated_width<=16) { // Removing items without reallocation.
+          if (npos1!=_width) std::memmove(_data+npos1,_data+npos2+1,sizeof(CImg<T>)*(_width - npos1));
+          std::memset(_data + _width,0,sizeof(CImg<T>)*nb);
+        } else { // Removing items with reallocation.
+          _allocated_width>>=2;
+          while (_allocated_width>16 && _width<(_allocated_width>>1)) _allocated_width>>=1;
+          CImg<T> *const new_data = new CImg<T>[_allocated_width];
+          if (npos1) std::memcpy(new_data,_data,sizeof(CImg<T>)*npos1);
+          if (npos1!=_width) std::memcpy(new_data+npos1,_data+npos2+1,sizeof(CImg<T>)*(_width-npos1));
+          if (_width!=_allocated_width) std::memset(new_data+_width,0,sizeof(_allocated_width - _width));
+          std::memset(_data,0,sizeof(CImg<T>)*(_width+nb));
+          delete[] _data;
+          _data = new_data;
+        }
+      }
+      return *this;
+    }
+
+    //! Remove all images between from indexes \newinstance.
+    CImgList<T> get_remove(const unsigned int pos1, const unsigned int pos2) const {
+      return (+*this).remove(pos1,pos2);
+    }
+
+    //! Remove image at index \c pos from the image list.
+    /**
+      \param pos Index of the image to remove.
+    **/
+    CImgList<T>& remove(const unsigned int pos) {
+      return remove(pos,pos);
+    }
+
+    //! Remove image at index \c pos from the image list \newinstance.
+    CImgList<T> get_remove(const unsigned int pos) const {
+      return (+*this).remove(pos);
+    }
+
+    //! Remove last image.
+    /**
+      \see remove().
+    **/
+    CImgList<T>& remove() {
+      return remove(_width-1);
+    }
+
+    //! Remove last image \newinstance.
+    CImgList<T> get_remove() const {
+      return (+*this).remove();
+    }
+
+    //! Reverse list order.
+    CImgList<T>& reverse() {
+      for (unsigned int l = 0; l<_width/2; ++l) (*this)[l].swap((*this)[_width-1-l]);
+      return *this;
+    }
+
+    //! Reverse list order \newinstance.
+    CImgList<T> get_reverse() const {
+      return (+*this).reverse();
+    }
+
+    //! Return a sublist.
+    /**
+      \param pos0 Starting index of the sublist.
+      \param pos1 Ending index of the sublist.
+    **/
+    CImgList<T>& images(const unsigned int pos0, const unsigned int pos1) {
+      return get_images(pos0,pos1).move_to(*this);
+    }
+
+    //! Return a sublist \newinstance.
+    CImgList<T> get_images(const unsigned int pos0, const unsigned int pos1) const {
+      if (pos0>pos1 || pos1>=_width)
+        throw CImgArgumentException(_cimglist_instance
+                                    "images() : Specified sub-list indices (%u->%u) are out of bounds.",
+                                    cimglist_instance,
+                                    pos0,pos1);
+      CImgList<T> res(pos1-pos0+1);
+      cimglist_for(res,l) res[l].assign(_data[pos0+l]);
+      return res;
+    }
+
+    //! Return a shared sublist.
+    /**
+      \param pos0 Starting index of the sublist.
+      \param pos1 Ending index of the sublist.
+    **/
+    CImgList<T> get_shared_images(const unsigned int pos0, const unsigned int pos1) {
+      if (pos0>pos1 || pos1>=_width)
+        throw CImgArgumentException(_cimglist_instance
+                                    "get_shared_images() : Specified sub-list indices (%u->%u) are out of bounds.",
+                                    cimglist_instance,
+                                    pos0,pos1);
+      CImgList<T> res(pos1-pos0+1);
+      cimglist_for(res,l) res[l].assign(_data[pos0+l],true);
+      return res;
+    }
+
+    //! Return a shared sublist \newinstance.
+    const CImgList<T> get_shared_images(const unsigned int pos0, const unsigned int pos1) const {
+      if (pos0>pos1 || pos1>=_width)
+        throw CImgArgumentException(_cimglist_instance
+                                    "get_shared_images() : Specified sub-list indices (%u->%u) are out of bounds.",
+                                    cimglist_instance,
+                                    pos0,pos1);
+      CImgList<T> res(pos1-pos0+1);
+      cimglist_for(res,l) res[l].assign(_data[pos0+l],true);
+      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 'c'.
+       \param align : specify the alignment for image concatenation. Can be '0' (top), '0.5' (center) or '1' (bottom) for instance.
+       \return A CImg<T> image corresponding to the concatenation is returned.
+    **/
+    CImg<T> get_append(const char axis, const float align=0) const {
+      if (is_empty()) return CImg<T>();
+      if (_width==1) return +((*this)[0]);
+      unsigned int dx = 0, dy = 0, dz = 0, dc = 0, pos = 0;
+      CImg<T> res;
+      switch (cimg::uncase(axis)) {
+      case 'x' : { // Along the X-axis.
+        cimglist_for(*this,l) {
+          const CImg<T>& img = (*this)[l];
+          if (img) { dx+=img._width; dy = cimg::max(dy,img._height); dz = cimg::max(dz,img._depth); dc = cimg::max(dc,img._spectrum); }
+        }
+        res.assign(dx,dy,dz,dc,0);
+        if (res) cimglist_for(*this,l) {
+            const CImg<T>& img = (*this)[l];
+            if (img) res.draw_image(pos,
+                                    (int)(align*(dy-img._height)),
+                                    (int)(align*(dz-img._depth)),
+                                    (int)(align*(dc-img._spectrum)),
+                                    img);
+            pos+=img._width;
+          }
+      } break;
+      case 'y' : { // Along the Y-axis.
+        cimglist_for(*this,l) {
+          const CImg<T>& img = (*this)[l];
+          if (img) { dx = cimg::max(dx,img._width); dy+=img._height; dz = cimg::max(dz,img._depth); dc = cimg::max(dc,img._spectrum); }
+        }
+        res.assign(dx,dy,dz,dc,0);
+        if (res) cimglist_for(*this,l) {
+            const CImg<T>& img = (*this)[l];
+            if (img) res.draw_image((int)(align*(dx-img._width)),
+                                    pos,
+                                    (int)(align*(dz-img._depth)),
+                                    (int)(align*(dc-img._spectrum)),
+                                    img);
+            pos+=img._height;
+          }
+      } break;
+      case 'z' : { // Along the Z-axis.
+        cimglist_for(*this,l) {
+          const CImg<T>& img = (*this)[l];
+          if (img) { dx = cimg::max(dx,img._width); dy = cimg::max(dy,img._height); dz+=img._depth; dc = cimg::max(dc,img._spectrum); }
+        }
+        res.assign(dx,dy,dz,dc,0);
+        if (res) cimglist_for(*this,l) {
+            const CImg<T>& img = (*this)[l];
+            if (img) res.draw_image((int)(align*(dx-img._width)),
+                                    (int)(align*(dy-img._height)),
+                                    pos,
+                                    (int)(align*(dc-img._spectrum)),
+                                    img);
+            pos+=img._depth;
+          }
+      } break;
+      default : { // Along the C-axis.
+        cimglist_for(*this,l) {
+          const CImg<T>& img = (*this)[l];
+          if (img) { dx = cimg::max(dx,img._width); dy = cimg::max(dy,img._height); dz = cimg::max(dz,img._depth); dc+=img._spectrum; }
+        }
+        res.assign(dx,dy,dz,dc,0);
+        if (res) cimglist_for(*this,l) {
+            const CImg<T>& img = (*this)[l];
+            if (img) res.draw_image((int)(align*(dx-img._width)),
+                                    (int)(align*(dy-img._height)),
+                                    (int)(align*(dz-img._depth)),
+                                    pos,
+                                    img);
+            pos+=img._spectrum;
+          }
+      }
+      }
+      return res;
+    }
+
+    //! Return a list where each image has been split along the specified axis.
+    /**
+        \param axis Axis to split images along.
+        \param nb Number of spliting parts for each image.
+    **/
+    CImgList<T>& split(const char axis, const int nb=0) {
+      return get_split(axis,nb).move_to(*this);
+    }
+
+    //! Return a list where each image has been split along the specified axis \newinstance.
+    CImgList<T> get_split(const char axis, const int nb=0) const {
+      CImgList<T> res;
+      cimglist_for(*this,l) _data[l].get_split(axis,nb).move_to(res,~0U);
+      return res;
+    }
+
+    //! Insert image at the end of the list.
+    /**
+      \param img Image to insert.
+    **/
+    template<typename t>
+    CImgList<T>& push_back(const CImg<t>& img) {
+      return insert(img);
+    }
+
+    //! Insert image at the front of the list.
+    /**
+      \param img Image to insert.
+    **/
+    template<typename t>
+    CImgList<T>& push_front(const CImg<t>& img) {
+      return insert(img,0);
+    }
+
+    //! Insert list at the end of the current list.
+    /**
+      \param list List to insert.
+    **/
+    template<typename t>
+    CImgList<T>& push_back(const CImgList<t>& list) {
+      return insert(list);
+    }
+
+    //! Insert list at the front of the current list.
+    /**
+      \param list List to insert.
+    **/
+    template<typename t>
+    CImgList<T>& push_front(const CImgList<t>& list) {
+      return insert(list,0);
+    }
+
+    //! Remove last image.
+    /**
+      \see remove().
+    **/
+    CImgList<T>& pop_back() {
+      return remove(_width-1);
+    }
+
+    //! Remove first image.
+    /**
+      \see remove().
+    **/
+    CImgList<T>& pop_front() {
+      return remove(0);
+    }
+
+    //! Remove image pointed by iterator.
+    /**
+      \param iter Iterator pointing to the image to remove.
+    **/
+    CImgList<T>& erase(const iterator iter) {
+      return remove(iter-_data);
+    }
+
+    //@}
+    //----------------------------------
+    //
+    //! \name Data Input
+    //@{
+    //----------------------------------
+
+    //! Display a simple interactive interface to select images or sublists.
+    /**
+       \param disp Window instance to display selection and user interface.
+       \param feature_type Can be \c false to select a single image, or \c true to select a sublist.
+       \param axis Axis along whom images are appended for visualization.
+       \param align Alignment setting when images have not all the same size.
+       \return A one-column vector containing the selected image indexes.
+    **/
+    CImg<intT> get_select(CImgDisplay &disp, const bool feature_type=true,
+                          const char axis='x', const float align=0) const {
+      return _get_select(disp,0,feature_type,axis,align,0,false,false,false);
+    }
+
+    //! Display a simple interactive interface to select images or sublists.
+    /**
+       \param title Title of a new window used to display selection and user interface.
+       \param feature_type Can be \c false to select a single image, or \c true to select a sublist.
+       \param axis Axis along whom images are appended for visualization.
+       \param align Alignment setting when images have not all the same size.
+       \return A one-column vector containing the selected image indexes.
+    **/
+    CImg<intT> get_select(const char *const title, const bool feature_type=true,
+                          const char axis='x', const float align=0) const {
+      CImgDisplay disp;
+      return _get_select(disp,title,feature_type,axis,align,0,false,false,false);
+    }
+
+    CImg<intT> _get_select(CImgDisplay &disp, const char *const title, const bool feature_type,
+                           const char axis, const float align,
+                           const unsigned int orig, const bool resize_disp,
+                           const bool exit_on_rightbutton, const bool exit_on_wheel) const {
+      if (is_empty())
+        throw CImgInstanceException(_cimglist_instance
+                                    "select() : Empty instance.",
+                                    cimglist_instance);
+
+      // Create image correspondence table and get list dimensions for visualization.
+      CImgList<uintT> _indices;
+      unsigned int max_width = 0, max_height = 0, sum_width = 0, sum_height = 0;
+      cimglist_for(*this,l) if (_data[l]) {
+        const CImg<T>& img = _data[l];
+        const unsigned int
+          w = CImgDisplay::_fitscreen(img._width,img._height,img._depth,128,-85,false),
+          h = CImgDisplay::_fitscreen(img._width,img._height,img._depth,128,-85,true);
+        if (w>max_width) max_width = w;
+        if (h>max_height) max_height = h;
+        sum_width+=w; sum_height+=h;
+        if (axis=='x') CImg<uintT>(w,1,1,1,(unsigned int)l).move_to(_indices);
+        else CImg<uintT>(h,1,1,1,(unsigned int)l).move_to(_indices);
+      }
+      const CImg<uintT> indices0 = _indices>'x';
+
+      // Create display window.
+      if (!disp) {
+        if (axis=='x') disp.assign(cimg_fitscreen(sum_width,max_height,1),title?title:0,1);
+        else disp.assign(cimg_fitscreen(max_width,sum_height,1),title?title:0,1);
+        if (!title) disp.set_title("CImgList<%s> (%u)",pixel_type(),_width);
+      } else if (title) disp.set_title("%s",title);
+      if (resize_disp) {
+        if (axis=='x') disp.resize(cimg_fitscreen(sum_width,max_height,1),false);
+        else disp.resize(cimg_fitscreen(max_width,sum_height,1),false);
+      }
+
+      const unsigned int old_normalization = disp.normalization();
+      bool old_is_resized = disp.is_resized();
+      disp._normalization = 0;
+      disp.show().set_key(0);
+      const unsigned char foreground_color[] = { 255,255,255 }, background_color[] = { 0,0,0 };
+
+      // Enter event loop.
+      CImg<ucharT> visu0, visu;
+      CImg<uintT> indices;
+      CImg<intT> positions(_width,4,1,1,-1);
+      int oindice0 = -1, oindice1 = -1, indice0 = -1, indice1 = -1;
+      bool is_clicked = false, is_selected = false, text_down = false, update_display = true;
+      unsigned int key = 0;
+      while (!is_selected && !disp.is_closed() && !key) {
+
+        // Create background image.
+        if (!visu0) {
+          visu0.assign(disp._width,disp._height,1,3,0); visu.assign();
+          (indices0.get_resize(axis=='x'?visu0._width:visu0._height,1)).move_to(indices);
+          unsigned int ind = 0;
+          if (axis=='x') for (unsigned int x = 0; x<visu0._width; ) {
+              const unsigned int x0 = x;
+              ind = indices[x];
+              while (x<indices._width && indices[++x]==ind) {}
+              const CImg<T>
+                &src = _data[ind],
+                _img2d = src._depth>1?src.get_projections2d(src._width/2,src._height/2,src._depth/2):CImg<T>(),
+                &img2d = _img2d?_img2d:src;
+              CImg<ucharT> res = old_normalization==1?
+                CImg<ucharT>(img2d.get_channels(0,cimg::min(2,img2d.spectrum()-1)).normalize(0,255)):
+                CImg<ucharT>(img2d.get_channels(0,cimg::min(2,img2d.spectrum()-1)));
+              const unsigned int h = CImgDisplay::_fitscreen(res._width,res._height,1,128,-85,true);
+              res.resize(x - x0,cimg::max(32U,h*disp._height/max_height),1,res._spectrum==1?3:-100);
+              positions(ind,0) = positions(ind,2) = (int)x0;
+              positions(ind,1) = positions(ind,3) = (int)(align*(visu0.height()-res.height()));
+              positions(ind,2)+=res._width;
+              positions(ind,3)+=res._height - 1;
+              visu0.draw_image(positions(ind,0),positions(ind,1),res);
+            } else for (unsigned int y = 0; y<visu0._height; ) {
+              const unsigned int y0 = y;
+              ind = indices[y];
+              while (y<visu0._height && indices[++y]==ind) {}
+              const CImg<T>
+                &src = _data[ind],
+                _img2d = src._depth>1?src.get_projections2d(src._width/2,src._height/2,src._depth/2):CImg<T>(),
+                &img2d = _img2d?_img2d:src;
+              CImg<ucharT> res = old_normalization==1?CImg<ucharT>(img2d.get_normalize(0,255)):CImg<ucharT>(img2d);
+              if (res._spectrum>3) res.channels(0,2);
+              const unsigned int w = CImgDisplay::_fitscreen(res._width,res._height,1,128,-85,false);
+              res.resize(cimg::max(32U,w*disp._width/max_width),y - y0,1,res._spectrum==1?3:-100);
+              positions(ind,0) = positions(ind,2) = (int)(align*(visu0.width()-res.width()));
+              positions(ind,1) = positions(ind,3) = (int)y0;
+              positions(ind,2)+=res._width - 1;
+              positions(ind,3)+=res._height;
+              visu0.draw_image(positions(ind,0),positions(ind,1),res);
+            }
+          if (axis=='x') --positions(ind,2); else --positions(ind,3);
+          update_display = true;
+        }
+
+        if (!visu || oindice0!=indice0 || oindice1!=indice1) {
+          if (indice0>=0 && indice1>=0) {
+            visu.assign(visu0,false);
+            const int indm = cimg::min(indice0,indice1), indM = cimg::max(indice0,indice1);
+            for (int ind = indm; ind<=indM; ++ind) if (positions(ind,0)>=0) {
+                visu.draw_rectangle(positions(ind,0),positions(ind,1),positions(ind,2),positions(ind,3),background_color,0.2f);
+                if ((axis=='x' && positions(ind,2) - positions(ind,0)>=8) ||
+                    (axis!='x' && positions(ind,3) - positions(ind,1)>=8))
+                  visu.draw_rectangle(positions(ind,0),positions(ind,1),positions(ind,2),positions(ind,3),foreground_color,0.9f,0x55555555);
+              }
+            const int yt = (int)text_down?visu.height()-13:0;
+            if (is_clicked) visu.draw_text(0,yt," Images %u - %u, Size = %u",foreground_color,background_color,0.7f,13,
+                                           orig + indm,orig + indM,indM - indm + 1);
+            else visu.draw_text(0,yt," Image %u",foreground_color,background_color,0.7f,13,
+                                orig + indice0);
+            update_display = true;
+          } else visu.assign();
+        }
+        if (!visu) { visu.assign(visu0,true); update_display = true; }
+        if (update_display) { visu.display(disp); update_display = false; }
+        disp.wait();
+
+        // Manage user events.
+        const int xm = disp.mouse_x(), ym = disp.mouse_y();
+        int indice = -1;
+
+        if (xm>=0) {
+          indice = (int)indices(axis=='x'?xm:ym);
+          if (disp.button()&1) {
+            if (!is_clicked) { is_clicked = true; oindice0 = indice0; indice0 = indice; }
+            oindice1 = indice1; indice1 = indice;
+            if (!feature_type) is_selected = true;
+          } else {
+            if (!is_clicked) { oindice0 = oindice1 = indice0; indice0 = indice1 = indice; }
+            else is_selected = true;
+          }
+        } else {
+          if (is_clicked) {
+            if (!(disp.button()&1)) { is_clicked = is_selected = false; indice0 = indice1 = -1; }
+            else indice1 = -1;
+          } else indice0 = indice1 = -1;
+        }
+
+        if (disp.button()&4) { is_clicked = is_selected = false; indice0 = indice1 = -1; }
+        if (disp.button()&2 && exit_on_rightbutton) { is_selected = true; indice1 = indice0 = -1; }
+        if (disp.wheel() && exit_on_wheel) is_selected = true;
+
+        switch (key = disp.key()) {
+#if cimg_OS!=2
+        case cimg::keyCTRLRIGHT :
+#endif
+        case 0 : case cimg::keyCTRLLEFT : key = 0; break;
+        case cimg::keyD : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) {
+            disp.set_fullscreen(false).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.set_key(key,false); key = 0; visu0.assign();
+          } break;
+        case cimg::keyC : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) {
+            disp.set_fullscreen(false).resize(cimg_fitscreen(2*disp.width()/3,2*disp.height()/3,1),false)._is_resized = true;
+            disp.set_key(key,false); key = 0; visu0.assign();
+          } break;
+        case cimg::keyR : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) {
+            disp.set_fullscreen(false).resize(cimg_fitscreen(axis=='x'?sum_width:max_width,axis=='x'?max_height:sum_height,1),false)._is_resized = true;
+            disp.set_key(key,false); key = 0; visu0.assign();
+          } break;
+        case cimg::keyF : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) {
+            disp.resize(disp.screen_width(),disp.screen_height(),false).toggle_fullscreen()._is_resized = true;
+            disp.set_key(key,false); key = 0; visu0.assign();
+          } break;
+        case cimg::keyS : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) {
+            static unsigned int snap_number = 0;
+            char filename[32] = { 0 };
+            std::FILE *file;
+            do {
+              cimg_snprintf(filename,sizeof(filename),cimg_appname "_%.4u.bmp",snap_number++);
+              if ((file=std::fopen(filename,"r"))!=0) cimg::fclose(file);
+            } while (file);
+            if (visu0) {
+              visu.draw_text(0,0," Saving snapshot... ",foreground_color,background_color,1,13).display(disp);
+              visu0.save(filename);
+              visu.draw_text(0,0," Snapshot '%s' saved. ",foreground_color,background_color,1,13,filename).display(disp);
+            }
+            disp.set_key(key,false).wait(); key = 0;
+          } break;
+        case cimg::keyO :
+          if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) {
+            static unsigned int snap_number = 0;
+            char filename[32] = { 0 };
+            std::FILE *file;
+            do {
+#ifdef cimg_use_zlib
+              cimg_snprintf(filename,sizeof(filename),cimg_appname "_%.4u.cimgz",snap_number++);
+#else
+              cimg_snprintf(filename,sizeof(filename),cimg_appname "_%.4u.cimg",snap_number++);
+#endif
+              if ((file=std::fopen(filename,"r"))!=0) cimg::fclose(file);
+            } while (file);
+            visu.draw_text(0,0," Saving instance... ",foreground_color,background_color,1,13).display(disp);
+            save(filename);
+            visu.draw_text(0,0," Instance '%s' saved. ",foreground_color,background_color,1,13,filename).display(disp);
+            disp.set_key(key,false).wait(); key = 0;
+          } break;
+        }
+        if (disp.is_resized()) { disp.resize(false); visu0.assign(); }
+        if (ym>=0 && ym<13) { if (!text_down) { visu.assign(); text_down = true; }}
+        else if (ym>=visu.height()-13) { if(text_down) { visu.assign(); text_down = false; }}
+      }
+      CImg<intT> res(1,2,1,1,-1);
+      if (is_selected) { if (feature_type) res.fill(cimg::min(indice0,indice1),cimg::max(indice0,indice1)); else res.fill(indice0); }
+      if (!(disp.button()&2)) disp.set_button();
+      disp._normalization = old_normalization;
+      disp._is_resized = old_is_resized;
+      disp.set_key(key);
+      return res;
+    }
+
+    //! Load a list from a file.
+    /**
+     \param filename Filename to read data from.
+     \see save(const char *).
+    **/
+    CImgList<T>& load(const char *const filename) {
+      if (!filename)
+        throw CImgArgumentException(_cimglist_instance
+                                    "load() : Specified filename is (null).",
+                                    cimglist_instance);
+
+      const char *const ext = cimg::split_filename(filename);
+      const unsigned int omode = cimg::exception_mode();
+      cimg::exception_mode() = 0;
+      try {
+#ifdef cimglist_load_plugin
+        cimglist_load_plugin(filename);
+#endif
+#ifdef cimglist_load_plugin1
+        cimglist_load_plugin1(filename);
+#endif
+#ifdef cimglist_load_plugin2
+        cimglist_load_plugin2(filename);
+#endif
+#ifdef cimglist_load_plugin3
+        cimglist_load_plugin3(filename);
+#endif
+#ifdef cimglist_load_plugin4
+        cimglist_load_plugin4(filename);
+#endif
+#ifdef cimglist_load_plugin5
+        cimglist_load_plugin5(filename);
+#endif
+#ifdef cimglist_load_plugin6
+        cimglist_load_plugin6(filename);
+#endif
+#ifdef cimglist_load_plugin7
+        cimglist_load_plugin7(filename);
+#endif
+#ifdef cimglist_load_plugin8
+        cimglist_load_plugin8(filename);
+#endif
+        if (!cimg::strcasecmp(ext,"tif") ||
+            !cimg::strcasecmp(ext,"tiff")) load_tiff(filename);
+        else if (!cimg::strcasecmp(ext,"cimg") ||
+                 !cimg::strcasecmp(ext,"cimgz") ||
+                 !*ext) load_cimg(filename);
+        else if (!cimg::strcasecmp(ext,"rec") ||
+                 !cimg::strcasecmp(ext,"par")) load_parrec(filename);
+        else if (!cimg::strcasecmp(ext,"avi") ||
+                 !cimg::strcasecmp(ext,"mov") ||
+                 !cimg::strcasecmp(ext,"asf") ||
+                 !cimg::strcasecmp(ext,"divx") ||
+                 !cimg::strcasecmp(ext,"flv") ||
+                 !cimg::strcasecmp(ext,"mpg") ||
+                 !cimg::strcasecmp(ext,"m1v") ||
+                 !cimg::strcasecmp(ext,"m2v") ||
+                 !cimg::strcasecmp(ext,"m4v") ||
+                 !cimg::strcasecmp(ext,"mjp") ||
+                 !cimg::strcasecmp(ext,"mkv") ||
+                 !cimg::strcasecmp(ext,"mpe") ||
+                 !cimg::strcasecmp(ext,"movie") ||
+                 !cimg::strcasecmp(ext,"ogm") ||
+                 !cimg::strcasecmp(ext,"ogg") ||
+                 !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);
+        else if (!cimg::strcasecmp(ext,"gz")) load_gzip_external(filename);
+        else throw CImgIOException("CImgList<%s>::load()",
+                                   pixel_type());
+      } catch (CImgIOException&) {
+        try {
+          std::fclose(cimg::fopen(filename,"rb"));
+        } catch (CImgIOException&) {
+          cimg::exception_mode() = omode;
+          throw CImgIOException(_cimglist_instance
+                                "load() : Failed to open file '%s'.",
+                                cimglist_instance,
+                                filename);
+        }
+        assign(1);
+        try {
+          _data->load(filename);
+        } catch (CImgIOException&) {
+          cimg::exception_mode() = omode;
+          throw CImgIOException(_cimglist_instance
+                                "load() : Failed to open file '%s'.",
+                                cimglist_instance,
+                                filename);
+        }
+      }
+      cimg::exception_mode() = omode;
+      return *this;
+    }
+
+    //! Load a list from a file \newinstance.
+    static CImgList<T> get_load(const char *const filename) {
+      return CImgList<T>().load(filename);
+    }
+
+    //! Load a list from a .cimg file.
+    /**
+      \param filename Filename to read data from.
+    **/
+    CImgList<T>& load_cimg(const char *const filename) {
+      return _load_cimg(0,filename);
+    }
+
+    //! Load a list from a .cimg file \newinstance.
+    static CImgList<T> get_load_cimg(const char *const filename) {
+      return CImgList<T>().load_cimg(filename);
+    }
+
+    //! Load a list from a .cimg file.
+    /**
+      \param file File to read data from.
+    **/
+    CImgList<T>& load_cimg(std::FILE *const file) {
+      return _load_cimg(file,0);
+    }
+
+    //! Load a list from a .cimg file \newinstance.
+    static CImgList<T> get_load_cimg(std::FILE *const file) {
+      return CImgList<T>().load_cimg(file);
+    }
+
+    CImgList<T>& _load_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,C); \
+   unsigned long destlen = (unsigned long)raw.size()*sizeof(Tss); \
+   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_instance \
+                         "load_cimg() : Unable to load compressed data from file '%s' unless zlib is enabled.", \
+                         cimglist_instance, \
+                         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=std::fgetc(nfile))!='\n' && i>=0) tmp[j++] = (char)i; tmp[j] = 0; \
+          W = H = D = C = 0; csiz = 0; \
+          if ((err = std::sscanf(tmp,"%u %u %u %u #%u",&W,&H,&D,&C,&csiz))<4) \
+            throw CImgIOException(_cimglist_instance \
+                                  "load_cimg() : Invalid specified size (%u,%u,%u,%u) of image %u in file '%s'", \
+                                  cimglist_instance, \
+                                  W,H,D,C,l,filename?filename:("(FILE*)")); \
+          if (W*H*D*C>0) { \
+            CImg<Tss> raw; \
+            CImg<T> &img = _data[l]; \
+            img.assign(W,H,D,C); \
+            T *ptrd = img._data; \
+            if (err==5) _cimgz_load_cimg_case(Tss) \
+            else for (long to_read = (long)img.size(); to_read>0; ) { \
+              raw.assign(cimg::min(to_read,cimg_iobuffer)); \
+              cimg::fread(raw._data,raw._width,nfile); \
+              if (endian!=cimg::endianness()) cimg::invert_endianness(raw._data,raw._width); \
+              to_read-=raw._width; \
+              const Tss *ptrs = raw._data; \
+              for (unsigned long off = (unsigned long)raw._width; off; --off) *(ptrd++) = (T)*(ptrs++); \
+            } \
+          } \
+        } \
+        loaded = true; \
+      }
+
+      if (!filename && !file)
+        throw CImgArgumentException(_cimglist_instance
+                                    "load_cimg() : Specified filename is (null).",
+                                    cimglist_instance);
+
+      const int cimg_iobuffer = 12*1024*1024;
+      std::FILE *const nfile = file?file:cimg::fopen(filename,"rb");
+      bool loaded = false, endian = cimg::endianness();
+      char tmp[256] = { 0 }, str_pixeltype[256] = { 0 }, str_endian[256] = { 0 };
+      unsigned int j, err, N = 0, W, H, D, C, csiz;
+      int i;
+      do {
+        j = 0; while ((i=std::fgetc(nfile))!='\n' && i!=EOF && j<256) tmp[j++] = (char)i; tmp[j] = 0;
+      } while (*tmp=='#' && i!=EOF);
+      err = 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_instance
+                              "load_cimg() : CImg header not found in file '%s'.",
+                              cimglist_instance,
+                              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",unsigned char);
+      _cimg_load_cimg_case("uchar",unsigned char);
+      _cimg_load_cimg_case("char",char);
+      _cimg_load_cimg_case("unsigned_short",unsigned short);
+      _cimg_load_cimg_case("ushort",unsigned short);
+      _cimg_load_cimg_case("short",short);
+      _cimg_load_cimg_case("unsigned_int",unsigned int);
+      _cimg_load_cimg_case("uint",unsigned int);
+      _cimg_load_cimg_case("int",int);
+      _cimg_load_cimg_case("unsigned_long",unsigned long);
+      _cimg_load_cimg_case("ulong",unsigned long);
+      _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_instance
+                              "load_cimg() : Unsupported pixel type '%s' for file '%s'.",
+                              cimglist_instance,
+                              str_pixeltype,filename?filename:"(FILE*)");
+      }
+      if (!file) cimg::fclose(nfile);
+      return *this;
+    }
+
+    //! Load a sublist list from a (non compressed) .cimg file.
+    /**
+      \param filename Filename to read data from.
+      \param n0 Starting index of images to read.
+      \param n1 Ending index of images to read.
+      \param x0 Starting X-coordinates of image regions to read.
+      \param y0 Starting Y-coordinates of image regions to read.
+      \param z0 Starting Z-coordinates of image regions to read.
+      \param c0 Starting C-coordinates of image regions to read.
+      \param x1 Ending X-coordinates of image regions to read.
+      \param y1 Ending Y-coordinates of image regions to read.
+      \param z1 Ending Z-coordinates of image regions to read.
+      \param c1 Ending C-coordinates of image regions to read.
+    **/
+    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 c0,
+                           const unsigned int x1, const unsigned int y1, const unsigned int z1, const unsigned int c1) {
+      return _load_cimg(0,filename,n0,n1,x0,y0,z0,c0,x1,y1,z1,c1);
+    }
+
+    //! Load a sublist list from a (non compressed) .cimg file \newinstance.
+    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 c0,
+                                     const unsigned int x1, const unsigned int y1, const unsigned int z1, const unsigned int c1) {
+      return CImgList<T>().load_cimg(filename,n0,n1,x0,y0,z0,c0,x1,y1,z1,c1);
+    }
+
+    //! Load a sub-image list from a (non compressed) .cimg file.
+    /**
+      \param file File to read data from.
+      \param n0 Starting index of images to read.
+      \param n1 Ending index of images to read.
+      \param x0 Starting X-coordinates of image regions to read.
+      \param y0 Starting Y-coordinates of image regions to read.
+      \param z0 Starting Z-coordinates of image regions to read.
+      \param c0 Starting C-coordinates of image regions to read.
+      \param x1 Ending X-coordinates of image regions to read.
+      \param y1 Ending Y-coordinates of image regions to read.
+      \param z1 Ending Z-coordinates of image regions to read.
+      \param c1 Ending C-coordinates of image regions to read.
+    **/
+    CImgList<T>& load_cimg(std::FILE *const file,
+                           const unsigned int n0, const unsigned int n1,
+                           const unsigned int x0, const unsigned int y0, const unsigned int z0, const unsigned int c0,
+                           const unsigned int x1, const unsigned int y1, const unsigned int z1, const unsigned int c1) {
+      return _load_cimg(file,0,n0,n1,x0,y0,z0,c0,x1,y1,z1,c1);
+    }
+
+    //! Load a sub-image list from a (non compressed) .cimg file \newinstance.
+    static CImgList<T> get_load_cimg(std::FILE *const file,
+                                     const unsigned int n0, const unsigned int n1,
+                                     const unsigned int x0, const unsigned int y0, const unsigned int z0, const unsigned int c0,
+                                     const unsigned int x1, const unsigned int y1, const unsigned int z1, const unsigned int c1) {
+      return CImgList<T>().load_cimg(file,n0,n1,x0,y0,z0,c0,x1,y1,z1,c1);
+    }
+
+    CImgList<T>& _load_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 c0,
+                            const unsigned int x1, const unsigned int y1, const unsigned int z1, const unsigned int c1) {
+#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=std::fgetc(nfile))!='\n' && i>=0) tmp[j++] = (char)i; tmp[j] = 0; \
+          W = H = D = C = 0; \
+          if (std::sscanf(tmp,"%u %u %u %u",&W,&H,&D,&C)!=4) \
+            throw CImgIOException(_cimglist_instance \
+                                  "load_cimg() : Invalid specified size (%u,%u,%u,%u) of image %u in file '%s'", \
+                                  cimglist_instance, \
+                                  W,H,D,C,l,filename?filename:"(FILE*)"); \
+          if (W*H*D*C>0) { \
+            if (l<n0 || x0>=W || y0>=H || z0>=D || c0>=D) std::fseek(nfile,W*H*D*C*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, \
+                nc1 = c1>=C?C-1:c1; \
+              CImg<Tss> raw(1 + nx1 - x0); \
+              CImg<T> &img = _data[l - n0]; \
+              img.assign(1 + nx1 - x0,1 + ny1 - y0,1 + nz1 - z0,1 + nc1 - c0); \
+              T *ptrd = img._data; \
+              const unsigned int skipvb = c0*W*H*D*sizeof(Tss); \
+              if (skipvb) std::fseek(nfile,skipvb,SEEK_CUR); \
+              for (unsigned int v = 1 + nc1 - c0; v; --v) { \
+                const unsigned int skipzb = z0*W*H*sizeof(Tss); \
+                if (skipzb) 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) std::fseek(nfile,skipyb,SEEK_CUR); \
+                  for (unsigned int y = 1 + ny1 - y0; y; --y) { \
+                    const unsigned int skipxb = x0*sizeof(Tss); \
+                    if (skipxb) 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) std::fseek(nfile,skipxe,SEEK_CUR); \
+                  } \
+                  const unsigned int skipye = (H-1-ny1)*W*sizeof(Tss); \
+                  if (skipye) std::fseek(nfile,skipye,SEEK_CUR); \
+                } \
+                const unsigned int skipze = (D-1-nz1)*W*H*sizeof(Tss); \
+                if (skipze) std::fseek(nfile,skipze,SEEK_CUR); \
+              } \
+              const unsigned int skipve = (C-1-nc1)*W*H*D*sizeof(Tss); \
+              if (skipve) std::fseek(nfile,skipve,SEEK_CUR); \
+            } \
+          } \
+        } \
+        loaded = true; \
+      }
+
+      if (!filename && !file)
+        throw CImgArgumentException(_cimglist_instance
+                                    "load_cimg() : Specified filename is (null).",
+                                    cimglist_instance);
+
+      if (n1<n0 || x1<x0 || y1<y0 || z1<z0 || c1<c0)
+        throw CImgArgumentException(_cimglist_instance
+                                    "load_cimg() : Invalid specified sub-region coordinates [%u->%u] (%u,%u,%u,%u)->(%u,%u,%u,%u) for file '%s'.",
+                                    cimglist_instance,
+                                    n0,n1,x0,y0,z0,c0,x1,y1,z1,filename?filename:"(FILE*)");
+
+      std::FILE *const nfile = file?file:cimg::fopen(filename,"rb");
+      bool loaded = false, endian = cimg::endianness();
+      char tmp[256] = { 0 }, str_pixeltype[256] = { 0 }, str_endian[256] = { 0 };
+      unsigned int j, err, N, W, H, D, C;
+      int i;
+      j = 0; while((i=std::fgetc(nfile))!='\n' && i!=EOF && j<256) tmp[j++] = (char)i; tmp[j] = 0;
+      err = 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_instance
+                              "load_cimg() : CImg header not found in file '%s'.",
+                              cimglist_instance,
+                              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",unsigned char);
+      _cimg_load_cimg_case2("uchar",unsigned char);
+      _cimg_load_cimg_case2("char",char);
+      _cimg_load_cimg_case2("unsigned_short",unsigned short);
+      _cimg_load_cimg_case2("ushort",unsigned short);
+      _cimg_load_cimg_case2("short",short);
+      _cimg_load_cimg_case2("unsigned_int",unsigned int);
+      _cimg_load_cimg_case2("uint",unsigned int);
+      _cimg_load_cimg_case2("int",int);
+      _cimg_load_cimg_case2("unsigned_long",unsigned long);
+      _cimg_load_cimg_case2("ulong",unsigned long);
+      _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_instance
+                              "load_cimg() : Unsupported pixel type '%s' for file '%s'.",
+                              cimglist_instance,
+                              str_pixeltype,filename?filename:"(FILE*)");
+      }
+      if (!file) cimg::fclose(nfile);
+      return *this;
+    }
+
+    //! Load a list from a PAR/REC (Philips) file.
+    /**
+      \param filename Filename to read data from.
+    **/
+    CImgList<T>& load_parrec(const char *const filename) {
+      if (!filename)
+        throw CImgArgumentException(_cimglist_instance
+                                    "load_parrec() : Specified filename is (null).",
+                                    cimglist_instance);
+
+      char body[1024] = { 0 }, filenamepar[1024] = { 0 }, filenamerec[1024] = { 0 };
+      const char *const ext = cimg::split_filename(filename,body);
+      if (!std::strcmp(ext,"par")) { std::strncpy(filenamepar,filename,sizeof(filenamepar)-1); cimg_snprintf(filenamerec,sizeof(filenamerec),"%s.rec",body); }
+      if (!std::strcmp(ext,"PAR")) { std::strncpy(filenamepar,filename,sizeof(filenamepar)-1); cimg_snprintf(filenamerec,sizeof(filenamerec),"%s.REC",body); }
+      if (!std::strcmp(ext,"rec")) { std::strncpy(filenamerec,filename,sizeof(filenamerec)-1); cimg_snprintf(filenamepar,sizeof(filenamepar),"%s.par",body); }
+      if (!std::strcmp(ext,"REC")) { std::strncpy(filenamerec,filename,sizeof(filenamerec)-1); cimg_snprintf(filenamepar,sizeof(filenamepar),"%s.PAR",body); }
+      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=std::fscanf(file,"%255[^\n]%*c",line); } while (err!=EOF && (*line=='#' || *line=='.'));
+      do {
+        unsigned int sn,sizex,sizey,pixsize;
+        float rs,ri,ss;
+        err = 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) {
+          CImg<floatT>::vector((float)sn,(float)pixsize,(float)sizex,(float)sizey,ri,rs,ss,0).move_to(st_slices);
+          unsigned int i; for (i = 0; i<st_global._width && sn<=st_global[i][2]; ++i) {}
+          if (i==st_global._width) CImg<uintT>::vector(sizex,sizey,sn).move_to(st_global);
+          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._width-1][7] = (float)i;
+        }
+      } while (err==7);
+
+      // Read data
+      std::FILE *file2 = cimg::fopen(filenamerec,"rb");
+      cimglist_for(st_global,l) {
+        const CImg<uintT>& vec = st_global[l];
+        CImg<T>(vec[0],vec[1],vec[2]).move_to(*this);
+      }
+
+      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(_cimglist_instance
+                                "load_parrec() : Unsupported %d-bits pixel type for file '%s'.",
+                                cimglist_instance,
+                                pixsize,filename);
+        }
+      }
+      cimg::fclose(file);
+      cimg::fclose(file2);
+      if (!_width)
+        throw CImgIOException(_cimglist_instance
+                              "load_parrec() : Failed to recognize valid PAR-REC data in file '%s'.",
+                              cimglist_instance,
+                              filename);
+      return *this;
+    }
+
+    //! Load a list from a PAR/REC (Philips) file \newinstance.
+    static CImgList<T> get_load_parrec(const char *const filename) {
+      return CImgList<T>().load_parrec(filename);
+    }
+
+    //! Load a list from a YUV image sequence file.
+    /**
+        \param filename Filename to read data from.
+        \param size_x Width of the images.
+        \param size_y Height of the images.
+        \param first_frame Index of first image frame to read.
+        \param last_frame Index of last image frame to read.
+        \param step_frame Step applied between each frame.
+        \param yuv2rgb Apply YUV to RGB transformation during reading.
+    **/
+    CImgList<T>& load_yuv(const char *const filename,
+                          const unsigned int size_x, const unsigned int size_y,
+                          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,size_x,size_y,first_frame,last_frame,step_frame,yuv2rgb);
+    }
+
+    //! Load a list from a YUV image sequence file \newinstance.
+    static CImgList<T> get_load_yuv(const char *const filename,
+                                    const unsigned int size_x, const unsigned int size_y=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,size_x,size_y,first_frame,last_frame,step_frame,yuv2rgb);
+    }
+
+    //! Load a list from an image sequence YUV file.
+    /**
+        \param filename File to read data from.
+        \param size_x Width of the images.
+        \param size_y Height of the images.
+        \param first_frame Index of first image frame to read.
+        \param last_frame Index of last image frame to read.
+        \param step_frame Step applied between each frame.
+        \param yuv2rgb Apply YUV to RGB transformation during reading.
+    **/
+    CImgList<T>& load_yuv(std::FILE *const file,
+                          const unsigned int size_x, const unsigned int size_y,
+                          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,size_x,size_y,first_frame,last_frame,step_frame,yuv2rgb);
+    }
+
+    //! Load a list from an image sequence YUV file \newinstance.
+    static CImgList<T> get_load_yuv(std::FILE *const file,
+                                    const unsigned int size_x, const unsigned int size_y=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,size_x,size_y,first_frame,last_frame,step_frame,yuv2rgb);
+    }
+
+    CImgList<T>& _load_yuv(std::FILE *const file, const char *const filename,
+                           const unsigned int size_x, const unsigned int size_y,
+                           const unsigned int first_frame, const unsigned int last_frame,
+                           const unsigned int step_frame, const bool yuv2rgb) {
+      if (!filename && !file)
+        throw CImgArgumentException(_cimglist_instance
+                                    "load_yuv() : Specified filename is (null).",
+                                    cimglist_instance);
+      if (size_x%2 || size_y%2)
+        throw CImgArgumentException(_cimglist_instance
+                                    "load_yuv() : Invalid odd XY dimensions %ux%u in file '%s'.",
+                                    cimglist_instance,
+                                    size_x,size_y,filename?filename:"(FILE*)");
+      if (!size_x || !size_y)
+        throw CImgArgumentException(_cimglist_instance
+                                    "load_yuv() : Invalid sequence size (%u,%u) in file '%s'.",
+                                    cimglist_instance,
+                                    size_x,size_y,filename?filename:"(FILE*)");
+
+      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(size_x,size_y,1,3), UV(size_x/2,size_y/2,1,2);
+      std::FILE *const nfile = file?file:cimg::fopen(filename,"rb");
+      bool stopflag = false;
+      int err;
+      if (nfirst_frame) {
+        err = std::fseek(nfile,nfirst_frame*(size_x*size_y + size_x*size_y/2),SEEK_CUR);
+        if (err) {
+          if (!file) cimg::fclose(nfile);
+          throw CImgIOException(_cimglist_instance
+                                "load_yuv() : File '%s' doesn't contain frame number %u.",
+                                cimglist_instance,
+                                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)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_instance
+                       "load_yuv() : File '%s' contains incomplete data or given image dimensions (%u,%u) are incorrect.",
+                       cimglist_instance,
+                       filename?filename:"(FILE*)",size_x,size_y);
+        } else {
+          UV.fill(0);
+          // *TRY* to read the luminance part, do not replace by cimg::fread !
+          err = (int)std::fread((void*)(UV._data),1,(size_t)(UV.size()),nfile);
+          if (err!=(int)(UV.size())) {
+            stopflag = true;
+            if (err>0)
+              cimg::warn(_cimglist_instance
+                         "load_yuv() : File '%s' contains incomplete data or given image dimensions (%u,%u) are incorrect.",
+                         cimglist_instance,
+                         filename?filename:"(FILE*)",size_x,size_y);
+          } 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) std::fseek(nfile,(nstep_frame-1)*(size_x*size_y + size_x*size_y/2),SEEK_CUR);
+          }
+        }
+      }
+      if (stopflag && nlast_frame!=~0U && frame!=nlast_frame)
+        cimg::warn(_cimglist_instance
+                   "load_yuv() : Frame %d not reached since only %u frames were found in file '%s'.",
+                   cimglist_instance,
+                   nlast_frame,frame-1,filename?filename:"(FILE*)");
+
+      if (!file) cimg::fclose(nfile);
+      return *this;
+    }
+
+    //! Load an image from a video file, using ffmpeg libraries.
+    /**
+      \param filename Filename to read data from.
+      \param first_frame Index of first image frame to read.
+      \param last_frame Index of last image frame to read.
+      \param step_frame Step applied between each frame.
+    **/
+    // 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_instance
+                                    "load_ffmpeg() : Specified filename is (null).",
+                                    cimglist_instance);
+
+      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(_cimglist_instance
+                                    "load_ffmpeg() : Unable to load sub-frames from file '%s' unless libffmpeg is enabled.",
+                                    cimglist_instance,
+                                    filename);
+
+      return load_ffmpeg_external(filename);
+#else
+      const PixelFormat 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_instance
+                                      "load_ffmpeg() : Failed to resume loading of file '%s', due to unallocated FFMPEG structures.",
+                                      cimglist_instance,
+                                      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_instance
+                                "load_ffmpeg() : Failed to open file '%s'.",
+                                cimglist_instance,
+                                filename);
+
+        if (!avframe || !converted_frame || av_find_stream_info(format_ctx)<0) {
+          av_close_input_file(format_ctx); format_ctx = 0;
+          return load_ffmpeg_external(filename);
+        }
+#if cimg_verbosity>=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) {
+                CImg<doubleT>::vector((double)packet.pts).move_to(timestamps);
+                ++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>'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) {
+          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) {
+          return load_ffmpeg_external(filename);
+        }
+        if (avcodec_open(codec_ctx,codec)<0) { // Open codec
+          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;
+#if defined(AV_VERSION_INT)
+#if LIBAVCODEC_VERSION_INT<AV_VERSION_INT(52,26,0)
+          avcodec_decode_video(codec_ctx,avframe,&decoded,packet.data, packet.size);
+#else
+          avcodec_decode_video2(codec_ctx,avframe,&decoded,&packet);
+#endif
+#else
+          avcodec_decode_video(codec_ctx,avframe,&decoded,packet.data, packet.size);
+#endif
+          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);
+                next_image._get_permute_axes("yzcx",foo).move_to(*this);
+              } else {
+                CImg<ucharT> next_image(*converted_frame->data,1,codec_ctx->width,codec_ctx->height,1,true);
+                next_image._get_permute_axes("yzcx",foo).move_to(*this);
+              }
+              next_frame+=nstep_frame;
+            }
+            ++frame;
+          }
+          av_free_packet(&packet);
+          if (next_frame>nlast_frame) break;
+        }
+      }
+      delete[] buffer;
+#endif
+      return *this;
+    }
+
+    //! Load an image from a video file, using ffmpeg libraries \newinstance.
+    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 using the external tool 'ffmpeg'.
+    /**
+      \param filename Filename to read data from.
+    **/
+    CImgList<T>& load_ffmpeg_external(const char *const filename) {
+      if (!filename)
+        throw CImgArgumentException(_cimglist_instance
+                                    "load_ffmpeg_external() : Specified filename is (null).",
+                                    cimglist_instance);
+      std::fclose(cimg::fopen(filename,"rb"));            // Check if file exists.
+      char command[1024] = { 0 }, filetmp[512] = { 0 }, filetmp2[512] = { 0 };
+      std::FILE *file = 0;
+      do {
+        cimg_snprintf(filetmp,sizeof(filetmp),"%s%c%s",cimg::temporary_path(),cimg_file_separator,cimg::filenamerand());
+        cimg_snprintf(filetmp2,sizeof(filetmp2),"%s_000001.ppm",filetmp);
+        if ((file=std::fopen(filetmp2,"rb"))!=0) cimg::fclose(file);
+      } while (file);
+      cimg_snprintf(filetmp2,sizeof(filetmp2),"%s_%%6d.ppm",filetmp);
+#if cimg_OS!=2
+      cimg_snprintf(command,sizeof(command),"%s -i \"%s\" %s >/dev/null 2>&1",cimg::ffmpeg_path(),filename,filetmp2);
+#else
+      cimg_snprintf(command,sizeof(command),"\"%s -i \"%s\" %s\" >NUL 2>&1",cimg::ffmpeg_path(),filename,filetmp2);
+#endif
+      cimg::system(command,0);
+      const unsigned int omode = cimg::exception_mode();
+      cimg::exception_mode() = 0;
+      assign();
+      unsigned int i = 1;
+      for (bool stopflag = false; !stopflag; ++i) {
+        cimg_snprintf(filetmp2,sizeof(filetmp2),"%s_%.6u.ppm",filetmp,i);
+        CImg<T> img;
+        try { img.load_pnm(filetmp2); }
+        catch (CImgException&) { stopflag = true; }
+        if (img) { img.move_to(*this); std::remove(filetmp2); }
+      }
+      cimg::exception_mode() = omode;
+      if (is_empty())
+        throw CImgIOException(_cimglist_instance
+                              "load_ffmpeg_external() : Failed to open file '%s' with external command 'ffmpeg'.",
+                              cimglist_instance,
+                              filename);
+      return *this;
+    }
+
+    //! Load an image from a video file using the external tool 'ffmpeg' \newinstance.
+    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'.
+    /**
+      \param filename Filename to read data from.
+    **/
+    CImgList<T>& load_gzip_external(const char *const filename) {
+      if (!filename)
+        throw CImgIOException(_cimglist_instance
+                              "load_gzip_external() : Specified filename is (null).",
+                              cimglist_instance);
+      std::fclose(cimg::fopen(filename,"rb"));            // Check if file exists.
+      char command[1024] = { 0 }, filetmp[512] = { 0 }, body[512] = { 0 };
+      const char
+        *ext = cimg::split_filename(filename,body),
+        *ext2 = cimg::split_filename(body,0);
+      std::FILE *file = 0;
+      do {
+        if (!cimg::strcasecmp(ext,"gz")) {
+          if (*ext2) cimg_snprintf(filetmp,sizeof(filetmp),"%s%c%s.%s",cimg::temporary_path(),cimg_file_separator,cimg::filenamerand(),ext2);
+          else cimg_snprintf(filetmp,sizeof(filetmp),"%s%c%s",cimg::temporary_path(),cimg_file_separator,cimg::filenamerand());
+        } else {
+          if (*ext) cimg_snprintf(filetmp,sizeof(filetmp),"%s%c%s.%s",cimg::temporary_path(),cimg_file_separator,cimg::filenamerand(),ext);
+          else cimg_snprintf(filetmp,sizeof(filetmp),"%s%c%s",cimg::temporary_path(),cimg_file_separator,cimg::filenamerand());
+        }
+        if ((file=std::fopen(filetmp,"rb"))!=0) cimg::fclose(file);
+      } while (file);
+      cimg_snprintf(command,sizeof(command),"%s -c \"%s\" > %s",cimg::gunzip_path(),filename,filetmp);
+      cimg::system(command);
+      if (!(file = std::fopen(filetmp,"rb"))) {
+        cimg::fclose(cimg::fopen(filename,"r"));
+        throw CImgIOException(_cimglist_instance
+                              "load_gzip_external() : Failed to open file '%s'.",
+                              cimglist_instance,
+                              filename);
+
+      } else cimg::fclose(file);
+      load(filetmp);
+      std::remove(filetmp);
+      return *this;
+    }
+
+    //! Load a gzipped list, using external tool 'gunzip' \newinstance.
+    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.
+    /**
+      \param filename Filename to read data from.
+      \param[out] primitives At return, contains the list of 3d object primitives.
+      \param[out] colors At return, contains the list of 3d object colors.
+      \return List of 3d object vertices.
+    **/
+    template<typename tf, typename tc>
+    CImgList<T>& load_off(const char *const filename,
+                          CImgList<tf>& primitives, CImgList<tc>& colors) {
+      return get_load_off(filename,primitives,colors).move_to(*this);
+    }
+
+    //! Load a 3d object from a .OFF file \newinstance.
+    template<typename tf, typename tc>
+      static CImgList<T> get_load_off(const char *const filename,
+                                      CImgList<tf>& primitives, CImgList<tc>& colors) {
+      return CImg<T>().load_off(filename,primitives,colors)<'x';
+    }
+
+    //! Load a multi-page TIFF file.
+    /**
+        \param filename Filename to read data from.
+        \param first_frame Index of first image frame to read.
+        \param last_frame Index of last image frame to read.
+        \param step_frame Step applied between each frame.
+    **/
+    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_instance
+                                    "load_tiff() : Unable to load sub-images from file '%s' unless libtiff is enabled.",
+                                    cimglist_instance,
+                                    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_instance
+                     "load_tiff() : Invalid specified frame range is [%u,%u] (step %u) since file '%s' contains %u image(s).",
+                     cimglist_instance,
+                     nfirst_frame,nlast_frame,nstep_frame,filename,nb_images);
+
+        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_verbosity>=3
+        TIFFSetWarningHandler(0);
+        TIFFSetErrorHandler(0);
+#endif
+        cimglist_for(*this,l) _data[l]._load_tiff(tif,nfirst_frame + l*nstep_frame);
+        TIFFClose(tif);
+      } else throw CImgIOException(_cimglist_instance
+                                   "load_tiff() : Failed to open file '%s'.",
+                                   cimglist_instance,
+                                   filename);
+      return *this;
+#endif
+    }
+
+    //! Load a multi-page TIFF file \newinstance.
+    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);
+    }
+
+    //@}
+    //----------------------------------
+    //
+    //! \name Data Output
+    //@{
+    //----------------------------------
+
+    //! Print informations about the list on the standard output.
+    /**
+      \param title Label set to the informations displayed.
+      \param display_stats Enable display of statistics.
+    **/
+    const CImgList<T>& print(const char *const title=0, const bool display_stats=true) const {
+      unsigned int 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 _title[64] = { 0 };
+      if (!title) cimg_snprintf(_title,sizeof(_title),"CImgList<%s>",pixel_type());
+      std::fprintf(cimg::output(),"%s%s%s%s : %sthis%s = %p, %ssize%s = %u/%u [%u %s], %sdata%s = (CImg<%s>*)%p",
+                   cimg::t_magenta,cimg::t_bold,title?title:_title,cimg::t_normal,
+                   cimg::t_bold,cimg::t_normal,(void*)this,
+                   cimg::t_bold,cimg::t_normal,_width,_allocated_width,
+                   mdisp==0?msiz:(mdisp==1?(msiz>>10):(msiz>>20)),
+                   mdisp==0?"b":(mdisp==1?"Kb":"Mb"),
+                   cimg::t_bold,cimg::t_normal,pixel_type(),(void*)begin());
+      if (_data) std::fprintf(cimg::output(),"..%p.\n",(void*)((char*)end()-1));
+      else std::fprintf(cimg::output(),".\n");
+
+      char tmp[16] = { 0 };
+      cimglist_for(*this,ll) {
+        cimg_snprintf(tmp,sizeof(tmp),"[%d]",ll);
+        std::fprintf(cimg::output(),"  ");
+        _data[ll].print(tmp,display_stats);
+        if (ll==3 && _width>8) { ll = _width-5; std::fprintf(cimg::output(),"  ...\n"); }
+      }
+      std::fflush(cimg::output());
+      return *this;
+    }
+
+    //! Display the current CImgList instance in an existing CImgDisplay window (by reference).
+    /**
+       \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 'c'.
+       \param align Specify the alignment for image concatenation.
+       \return A reference to the current CImgList instance is returned.
+       \note 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.
+    **/
+    const CImgList<T>& display(CImgDisplay &disp, const char axis='x', const float align=0) const {
+      get_append(axis,align).display(disp);
+      return *this;
+    }
+
+    //! Display the current CImgList instance in a new display window.
+    /**
+       \param title Title of the opening display window.
+       \param axis Axis for image concatenation. Can be 'x','y','z' or 'c'.
+       \param align Alignment for image concatenation.
+       \return A reference to the current CImgList instance is returned.
+       \note 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.
+    **/
+    const CImgList<T>& display(CImgDisplay &disp, const bool display_info,
+                               const char axis='x', const float align=0) const {
+      bool is_exit = false;
+      return _display(disp,0,display_info,axis,align,0,true,is_exit);
+    }
+
+    //! Display the current CImgList instance in a new display window.
+    /**
+      \param title Title of the opening display window.
+      \param display_info Flag telling if list informations must be written on standard output.
+      \param axis Axis for image concatenation. Can be 'x','y','z' or 'c'.
+      \param Alignment for image concatenation.
+    **/
+    const CImgList<T>& display(const char *const title=0, const bool display_info=true,
+                               const char axis='x', const float align=0) const {
+      CImgDisplay disp;
+      bool is_exit = false;
+      return _display(disp,title,display_info,axis,align,0,true,is_exit);
+    }
+
+    const CImgList<T>& _display(CImgDisplay &disp, const char *const title, const bool display_info,
+                                const char axis, const float align,
+                                const unsigned int orig, const bool is_first_call, bool &is_exit) const {
+      if (is_empty())
+        throw CImgInstanceException(_cimglist_instance
+                                    "display() : Empty instance.",
+                                    cimglist_instance);
+      if (!disp) {
+        if (axis=='x') {
+          unsigned int sum_width = 0, max_height = 0;
+          cimglist_for(*this,l) {
+            const CImg<T> &img = _data[l];
+            const unsigned int
+              w = CImgDisplay::_fitscreen(img._width,img._height,img._depth,128,-85,false),
+              h = CImgDisplay::_fitscreen(img._width,img._height,img._depth,128,-85,true);
+            sum_width+=w;
+            if (h>max_height) max_height = h;
+          }
+          disp.assign(cimg_fitscreen(sum_width,max_height,1),title?title:0,1);
+        } else {
+          unsigned int max_width = 0, sum_height = 0;
+          cimglist_for(*this,l) {
+            const CImg<T> &img = _data[l];
+            const unsigned int
+              w = CImgDisplay::_fitscreen(img._width,img._height,img._depth,128,-85,false),
+              h = CImgDisplay::_fitscreen(img._width,img._height,img._depth,128,-85,true);
+            if (w>max_width) max_width = w;
+            sum_height+=h;
+          }
+          disp.assign(cimg_fitscreen(max_width,sum_height,1),title?title:0,1);
+        }
+        if (!title) disp.set_title("CImgList<%s> (%u)",pixel_type(),_width);
+      } else if (title) disp.set_title("%s",title);
+      const CImg<char> dtitle = CImg<char>::string(disp.title());
+      if (display_info) print(disp.title());
+      disp.show().flush();
+
+      if (_width==1) {
+        if (!is_first_call)
+          disp.resize(cimg_fitscreen(_data[0]._width,_data[0]._height,_data[0]._depth),false).
+            set_title("%s (%ux%ux%ux%u)",dtitle.data(),_data[0]._width,_data[0]._height,_data[0]._depth,_data[0]._spectrum);
+        _data[0]._display(disp,0,false,!is_first_call);
+        if (disp.key()) is_exit = true;
+        disp.set_title("%s",dtitle.data());
+      } else {
+        bool disp_resize = !is_first_call;
+        while (!disp.is_closed() && !is_exit) {
+          const CImg<intT> s = _get_select(disp,0,true,axis,align,orig,disp_resize,!is_first_call,true);
+          disp_resize = true;
+          if (s[0]<0) { // No selections done.
+            if (disp.button()&2) { disp.flush(); break; }
+            is_exit = true;
+          } else if (disp.wheel()) { // Zoom in/out.
+            const int wheel = disp.wheel();
+            disp.set_wheel();
+            if (!is_first_call && wheel<0) break;
+            if (wheel>0 && _width>=4) {
+              const unsigned int
+                delta = cimg::max(1U,(unsigned int)cimg::round(0.3*_width)),
+                ind0 = (unsigned int)cimg::max(0,s[0] - (int)delta),
+                ind1 = (unsigned int)cimg::min(width() - 1,s[0] + (int)delta);
+              if ((ind0!=0 || ind1!=_width-1) && ind1 - ind0>=3) get_shared_images(ind0,ind1)._display(disp,0,false,axis,align,orig + ind0,false,is_exit);
+            }
+          } else if (s[0]!=0 || s[1]!=width()-1) get_shared_images(s[0],s[1])._display(disp,0,false,axis,align,orig+s[0],false,is_exit);
+        }
+      }
+      return *this;
+    }
+
+    //! Save a list into a file.
+    /**
+      \param filename Filename to write data to.
+      \param number Number of digits used when chosen format requires the saving of multiple files.
+    **/
+    const CImgList<T>& save(const char *const filename, const int number=-1) const {
+      if (!filename)
+        throw CImgArgumentException(_cimglist_instance
+                                    "save() : Specified filename is (null).",
+                                    cimglist_instance);
+      // Do not test for empty instances, since .cimg format is able to manage empty instances.
+      const char *const ext = cimg::split_filename(filename);
+      char nfilename[1024] = { 0 };
+      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
+      if (!cimg::strcasecmp(ext,"cimgz")) return save_cimg(fn,true);
+      else if (!cimg::strcasecmp(ext,"cimg") || !*ext) return save_cimg(fn,false);
+      else if (!cimg::strcasecmp(ext,"yuv")) return save_yuv(fn,true);
+      else if (!cimg::strcasecmp(ext,"avi") ||
+               !cimg::strcasecmp(ext,"mov") ||
+               !cimg::strcasecmp(ext,"asf") ||
+               !cimg::strcasecmp(ext,"divx") ||
+               !cimg::strcasecmp(ext,"flv") ||
+               !cimg::strcasecmp(ext,"mpg") ||
+               !cimg::strcasecmp(ext,"m1v") ||
+               !cimg::strcasecmp(ext,"m2v") ||
+               !cimg::strcasecmp(ext,"m4v") ||
+               !cimg::strcasecmp(ext,"mjp") ||
+               !cimg::strcasecmp(ext,"mkv") ||
+               !cimg::strcasecmp(ext,"mpe") ||
+               !cimg::strcasecmp(ext,"movie") ||
+               !cimg::strcasecmp(ext,"ogm") ||
+               !cimg::strcasecmp(ext,"ogg") ||
+               !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);
+#ifdef cimg_use_tiff
+      else if (!cimg::strcasecmp(ext,"tif") ||
+          !cimg::strcasecmp(ext,"tiff")) return save_tiff(fn);
+#endif
+      else if (!cimg::strcasecmp(ext,"gz")) return save_gzip_external(fn);
+      else { if (_width==1) _data[0].save(fn,-1); else cimglist_for(*this,l) _data[l].save(fn,l); }
+      return *this;
+    }
+
+    // Tell if a CImgList can be saved as one single file.
+    static bool is_saveable(const char *const filename) {
+      const char *const ext = cimg::split_filename(filename);
+      if (!cimg::strcasecmp(ext,"cimgz") ||
+#ifdef cimg_use_tiff
+          !cimg::strcasecmp(ext,"tif") ||
+          !cimg::strcasecmp(ext,"tiff") ||
+#endif
+          !cimg::strcasecmp(ext,"yuv") ||
+          !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,"ogg") ||
+          !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 true;
+      return false;
+    }
+
+    //! Save an image sequence, using FFMPEG library.
+    /**
+      \param filename Filename to write data to.
+      \param first_frame Index of first image frame to write.
+      \param last_frame Index of last image frame to write.
+      \param fps Desired framerate (in frames per seconds) if chosen format supports it.
+      \param bitrate Desired bitrate (in bits per seconds) if chosen format supports it.
+    **/
+    // 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 unsigned int bitrate=2048) const {
+      if (!filename)
+        throw CImgArgumentException(_cimglist_instance
+                                    "save_ffmpeg() : Specified filename is (null).",
+                                    cimglist_instance);
+      if (is_empty())
+        throw CImgInstanceException(_cimglist_instance
+                                    "save_ffmpeg() : Empty instance, for file '%s'.",
+                                    cimglist_instance,
+                                    filename);
+      if (!fps)
+        throw CImgArgumentException(_cimglist_instance
+                                    "save_ffmpeg() : Invalid specified framerate 0, for file '%s'.",
+                                    cimglist_instance,
+                                    filename);
+
+      const unsigned int nlast_frame = last_frame==~0U?_width-1:last_frame;
+      if (first_frame>=_width || nlast_frame>=_width)
+        throw CImgArgumentException(_cimglist_instance
+                                    "save_ffmpeg() : Out of range specified frames [%u,%u], for file '%s'.",
+                                    cimglist_instance,
+                                    first_frame,last_frame,filename);
+
+      for (unsigned int ll = first_frame; ll<=nlast_frame; ++ll) if (!_data[ll].is_sameXYZ(_data[0]))
+        throw CImgInstanceException(_cimglist_instance
+                                    "save_ffmpeg() : Invalid instance dimensions, for file '%s'.",
+                                    cimglist_instance,
+                                    filename);
+
+#ifndef cimg_use_ffmpeg
+      return save_ffmpeg_external(filename,first_frame,last_frame,0,fps,bitrate);
+#else
+      avcodec_register_all();
+      av_register_all();
+      const int
+        frame_dimx = _data[first_frame].width(),
+        frame_dimy = _data[first_frame].height(),
+        frame_dimv = _data[first_frame].spectrum();
+      if (frame_dimv!=1 && frame_dimv!=3)
+        throw CImgInstanceException(_cimglist_instance
+                                    "save_ffmpeg() : Image[0] (%u,%u,%u,%u,%p) has not 1 or 3 channels, for file '%s'.",
+                                    cimglist_instance,
+                                    _data[0]._width,_data[0]._height,_data[0]._depth,_data[0]._spectrum,_data,filename);
+
+      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;
+#if defined(AV_VERSION_INT)
+#if LIBAVFORMAT_VERSION_INT<AV_VERSION_INT(52,45,0)
+      fmt = guess_format(0,filename,0);
+      if (!fmt) fmt = guess_format("mpeg",0,0); // Default format "mpeg".
+#else
+      fmt = av_guess_format(0,filename,0);
+      if (!fmt) fmt = av_guess_format("mpeg",0,0); // Default format "mpeg".
+#endif
+#else
+      fmt = guess_format(0,filename,0);
+      if (!fmt) fmt = guess_format("mpeg",0,0); // Default format "mpeg".
+#endif
+
+      if (!fmt)
+        throw CImgArgumentException(_cimglist_instance
+                                    "save_ffmpeg() : Unable to determine codec for file '%s'.",
+                                    cimglist_instance,
+                                    filename);
+
+      AVFormatContext *oc = 0;
+#if defined(AV_VERSION_INT)
+#if LIBAVFORMAT_VERSION_INT<AV_VERSION_INT(52,36,0)
+      oc = av_alloc_format_context();
+#else
+      oc = avformat_alloc_context();
+#endif
+#else
+      oc = av_alloc_format_context();
+#endif
+      if (!oc) // Failed to allocate format context.
+        throw CImgIOException(_cimglist_instance
+                              "save_ffmpeg() : Failed to allocate FFMPEG structure for format context, for file '%s'.",
+                              cimglist_instance,
+                              filename);
+
+      AVCodec *codec = 0;
+      AVFrame *picture = 0;
+      AVFrame *tmp_pict = 0;
+      oc->oformat = fmt;
+      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_instance
+                                "save_ffmpeg() : Failed to allocate FFMPEG structure for video stream, for file '%s'.",
+                                cimglist_instance,
+                                filename);
+        }
+      } else { // No codec identified.
+        av_free(oc);
+        throw CImgIOException(_cimglist_instance
+                              "save_ffmpeg() : Failed to identify proper codec, for file '%s'.",
+                              cimglist_instance,
+                              filename);
+      }
+
+      AVCodecContext *c = video_str->codec;
+      c->codec_id = fmt->video_codec;
+      c->codec_type = CODEC_TYPE_VIDEO;
+      c->bit_rate = 1024*bitrate;
+      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_instance
+                              "save_ffmpeg() : Invalid parameters set for avcodec, for file '%s'.",
+                              cimglist_instance,
+                              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_instance
+                              "save_ffmpeg() : No valid codec found for file '%s'.",
+                              cimglist_instance,
+                              filename);
+      }
+      if (avcodec_open(c,codec)<0) // Failed to open codec.
+        throw CImgIOException(_cimglist_instance
+                              "save_ffmpeg() : Failed to open codec for file '%s'.",
+                              cimglist_instance,
+                              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_instance
+                              "save_ffmpeg() : Failed to allocate memory for file '%s'.",
+                              cimglist_instance,
+                              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_instance
+                              "save_ffmpeg() : Failed to allocate memory for file '%s'.",
+                              cimglist_instance,
+                              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_instance
+                              "save_ffmpeg() : Failed to allocate memory for file '%s'.",
+                              cimglist_instance,
+                              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_instance
+                              "save_ffmpeg() : Failed to allocate memory for file '%s'.",
+                              cimglist_instance,
+                              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_instance
+                                "save_ffmpeg() : Failed to open file '%s'.",
+                                cimglist_instance,
+                                filename);
+      }
+
+      if (av_write_header(oc)<0)
+        throw CImgIOException(_cimglist_instance
+                              "save_ffmpeg() : Failed to write header in file '%s'.",
+                              cimglist_instance,
+                              filename);
+
+      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_instance
+                              "save_ffmpeg() : Failed to get conversion context for file '%s'.",
+                              cimglist_instance,
+                              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_instance
+                              "save_ffmpeg() : Failed to allocate memory, for file '%s'.",
+                              cimglist_instance,
+                              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) 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_instance
+                              "save_ffmpeg() : Failed to write trailer for file '%s'.",
+                              cimglist_instance,
+                              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_instance
+                                "save_ffmpeg() : File '%s', failed to close file.",
+                                cimglist_instance,
+                                filename);
+        */
+      }
+      av_free(oc);
+      av_free(video_outbuf);
+#endif
+      return *this;
+    }
+
+    const CImgList<T>& _save_yuv(std::FILE *const file, const char *const filename, const bool rgb2yuv) const {
+      if (!file && !filename)
+        throw CImgArgumentException(_cimglist_instance
+                                    "save_yuv() : Specified filename is (null).",
+                                    cimglist_instance);
+      if (is_empty())
+        throw CImgInstanceException(_cimglist_instance
+                                    "save_yuv() : Empty instance, for file '%s'.",
+                                    cimglist_instance,
+                                    filename?filename:"(FILE*)");
+
+      if ((*this)[0].width()%2 || (*this)[0].height()%2)
+        throw CImgInstanceException(_cimglist_instance
+                                    "save_yuv() : Invalid odd instance dimensions (%u,%u) for file '%s'.",
+                                    cimglist_instance,
+                                    (*this)[0].width(),(*this)[0].height(),
+                                    filename?filename:"(FILE*)");
+
+      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).data(0,0,0,1),
+                     YCbCr._width*YCbCr._height/2,nfile);
+      }
+      if (!file) cimg::fclose(nfile);
+      return *this;
+    }
+
+    //! Save list as a YUV image sequence file.
+    /**
+      \param filename Filename to write data to.
+      \param rgb2yuv Flag telling if the RGB to YUV conversion must be done for saving.
+    **/
+    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.
+    /**
+      \param file File to write data to.
+      \param rgb2yuv Flag telling if the RGB to YUV conversion must be done for saving.
+    **/
+    const CImgList<T>& save_yuv(std::FILE *const file, const bool rgb2yuv=true) const {
+      return _save_yuv(file,0,rgb2yuv);
+    }
+
+    const CImgList<T>& _save_cimg(std::FILE *const file, const char *const filename, const bool compression) const {
+      if (!file && !filename)
+        throw CImgArgumentException(_cimglist_instance
+                                    "save_cimg() : Specified filename is (null).",
+                                    cimglist_instance);
+#ifndef cimg_use_zlib
+      if (compression)
+        cimg::warn(_cimglist_instance
+                   "save_cimg() : Unable to save compressed data in file '%s' unless zlib is enabled, saving them uncompressed.",
+                   cimglist_instance,
+                   filename?filename:"(FILE*)");
+#endif
+      std::FILE *const nfile = file?file:cimg::fopen(filename,"wb");
+      const char *const ptype = pixel_type(), *const etype = cimg::endianness()?"big":"little";
+      if (std::strstr(ptype,"unsigned")==ptype) std::fprintf(nfile,"%u unsigned_%s %s_endian\n",_width,ptype+9,etype);
+      else std::fprintf(nfile,"%u %s %s_endian\n",_width,ptype,etype);
+      cimglist_for(*this,l) {
+        const CImg<T>& img = _data[l];
+        std::fprintf(nfile,"%u %u %u %u",img._width,img._height,img._depth,img._spectrum);
+        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/100 + 16;
+            Bytef *const cbuf = new Bytef[csiz];
+            if (compress(cbuf,&csiz,(Bytef*)ref._data,siz)) {
+              cimg::warn(_cimglist_instance
+                         "save_cimg() : Failed to save compressed data for file '%s', saving them uncompressed.",
+                         cimglist_instance,
+                         filename?filename:"(FILE*)");
+
+              compressed = false;
+            } else {
+              std::fprintf(nfile," #%lu\n",csiz);
+              cimg::fwrite(cbuf,csiz,nfile);
+              delete[] cbuf;
+              compressed = true;
+            }
+#else
+            compressed = false;
+#endif
+          }
+          if (!compressed) {
+            std::fputc('\n',nfile);
+            cimg::fwrite(ref._data,ref.size(),nfile);
+          }
+        } else std::fputc('\n',nfile);
+      }
+      if (!file) cimg::fclose(nfile);
+      return *this;
+    }
+
+    //! Save a list into a .cimg file.
+    /**
+       \param filename Filename to write data to.
+       \param compress Flag telling if data compression must be enabled.
+    **/
+    const CImgList<T>& save_cimg(const char *const filename, const bool compress=false) const {
+      return _save_cimg(0,filename,compress);
+    }
+
+    //! Save a list into a .cimg file.
+    /**
+       \param file File to write data to.
+       \param compress Flag telling if data compression must be enabled.
+    **/
+    const CImgList<T>& save_cimg(std::FILE *file, const bool compress=false) const {
+      return _save_cimg(file,0,compress);
+    }
+
+    const CImgList<T>& _save_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 c0) 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=std::fgetc(nfile))!='\n') tmp[j++]=(char)i; tmp[j] = 0; \
+          W = H = D = C = 0; \
+          if (std::sscanf(tmp,"%u %u %u %u",&W,&H,&D,&C)!=4) \
+            throw CImgIOException(_cimglist_instance \
+                                  "save_cimg() : Invalid size (%u,%u,%u,%u) of image[%u], for file '%s'.", \
+                                  cimglist_instance, \
+                                  W,H,D,C,l,filename?filename:"(FILE*)"); \
+          if (W*H*D*C>0) { \
+            if (l<n0 || x0>=W || y0>=H || z0>=D || c0>=D) std::fseek(nfile,W*H*D*C*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, \
+                c1 = c0 + img._spectrum - 1, \
+                nx1 = x1>=W?W-1:x1, \
+                ny1 = y1>=H?H-1:y1, \
+                nz1 = z1>=D?D-1:z1, \
+                nc1 = c1>=C?C-1:c1; \
+              CImg<Tss> raw(1+nx1-x0); \
+              const unsigned int skipvb = c0*W*H*D*sizeof(Tss); \
+              if (skipvb) std::fseek(nfile,skipvb,SEEK_CUR); \
+              for (unsigned int v = 1 + nc1 - c0; v; --v) { \
+                const unsigned int skipzb = z0*W*H*sizeof(Tss); \
+                if (skipzb) 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) std::fseek(nfile,skipyb,SEEK_CUR); \
+                  for (unsigned int y = 1 + ny1 - y0; y; --y) { \
+                    const unsigned int skipxb = x0*sizeof(Tss); \
+                    if (skipxb) 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) std::fseek(nfile,skipxe,SEEK_CUR); \
+                  } \
+                  const unsigned int skipye = (H - 1 - ny1)*W*sizeof(Tss); \
+                  if (skipye) std::fseek(nfile,skipye,SEEK_CUR); \
+                } \
+                const unsigned int skipze = (D - 1 - nz1)*W*H*sizeof(Tss); \
+                if (skipze) std::fseek(nfile,skipze,SEEK_CUR); \
+              } \
+              const unsigned int skipve = (C - 1 - nc1)*W*H*D*sizeof(Tss); \
+              if (skipve) std::fseek(nfile,skipve,SEEK_CUR); \
+            } \
+          } \
+        } \
+        saved = true; \
+      }
+
+      if (!file && !filename)
+        throw CImgArgumentException(_cimglist_instance
+                                    "save_cimg() : Specified filename is (null).",
+                                    cimglist_instance);
+      if (is_empty())
+        throw CImgInstanceException(_cimglist_instance
+                                    "save_cimg() : Empty instance, for file '%s'.",
+                                    cimglist_instance,
+                                    filename?filename:"(FILE*)");
+
+      std::FILE *const nfile = file?file:cimg::fopen(filename,"rb+");
+      bool saved = false, endian = cimg::endianness();
+      char tmp[256] = { 0 }, str_pixeltype[256] = { 0 }, str_endian[256] = { 0 };
+      unsigned int j, err, N, W, H, D, C;
+      int i;
+      j = 0; while((i=std::fgetc(nfile))!='\n' && i!=EOF && j<256) tmp[j++] = (char)i; tmp[j] = 0;
+      err = 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_instance
+                              "save_cimg() : CImg header not found in file '%s'.",
+                              cimglist_instance,
+                              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+_width);
+      _cimg_save_cimg_case("bool",bool);
+      _cimg_save_cimg_case("unsigned_char",unsigned char);
+      _cimg_save_cimg_case("uchar",unsigned char);
+      _cimg_save_cimg_case("char",char);
+      _cimg_save_cimg_case("unsigned_short",unsigned short);
+      _cimg_save_cimg_case("ushort",unsigned short);
+      _cimg_save_cimg_case("short",short);
+      _cimg_save_cimg_case("unsigned_int",unsigned int);
+      _cimg_save_cimg_case("uint",unsigned int);
+      _cimg_save_cimg_case("int",int);
+      _cimg_save_cimg_case("unsigned_long",unsigned long);
+      _cimg_save_cimg_case("ulong",unsigned long);
+      _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_instance
+                              "save_cimg() : Unsupported data type '%s' for file '%s'.",
+                              cimglist_instance,
+                              filename?filename:"(FILE*)",str_pixeltype);
+      }
+      if (!file) cimg::fclose(nfile);
+      return *this;
+    }
+
+    //! Insert the image instance into into an existing .cimg file, at specified coordinates.
+    /**
+      \param filename Filename to write data to.
+      \param n0 Starting index of images to write.
+      \param n1 Ending index of images to write.
+      \param x0 Starting X-coordinates of image regions to write.
+      \param y0 Starting Y-coordinates of image regions to write.
+      \param z0 Starting Z-coordinates of image regions to write.
+      \param c0 Starting C-coordinates of image regions to write.
+    **/
+    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 c0) const {
+      return _save_cimg(0,filename,n0,x0,y0,z0,c0);
+    }
+
+    //! Insert the image instance into into an existing .cimg file, at specified coordinates.
+    /**
+      \param file File to write data to.
+      \param n0 Starting index of images to write.
+      \param n1 Ending index of images to write.
+      \param x0 Starting X-coordinates of image regions to write.
+      \param y0 Starting Y-coordinates of image regions to write.
+      \param z0 Starting Z-coordinates of image regions to write.
+      \param c0 Starting C-coordinates of image regions to write.
+    **/
+    const CImgList<T>& save_cimg(std::FILE *const file,
+                                 const unsigned int n0,
+                                 const unsigned int x0, const unsigned int y0,
+                                 const unsigned int z0, const unsigned int c0) const {
+      return _save_cimg(file,0,n0,x0,y0,z0,c0);
+    }
+
+    static void _save_empty_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 dc) {
+      std::FILE *const nfile = file?file:cimg::fopen(filename,"wb");
+      const unsigned int siz = dx*dy*dz*dc*sizeof(T);
+      std::fprintf(nfile,"%u %s\n",nb,pixel_type());
+      for (unsigned int i=nb; i; --i) {
+        std::fprintf(nfile,"%u %u %u %u\n",dx,dy,dz,dc);
+        for (unsigned int off=siz; off; --off) std::fputc(0,nfile);
+      }
+      if (!file) cimg::fclose(nfile);
+    }
+
+    //! Create an empty (non-compressed) .cimg file with specified dimensions.
+    /**
+        \param filename Filename to write data to.
+        \param nb Number of images to write.
+        \param dx Width of images in the written file.
+        \param dy Height of images in the written file.
+        \param dz Depth of images in the written file.
+        \param dc Spectrum of images in the written file.
+    **/
+    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 dc=1) {
+      return _save_empty_cimg(0,filename,nb,dx,dy,dz,dc);
+    }
+
+    //! Create an empty .cimg file with specified dimensions.
+    /**
+        \param file File to write data to.
+        \param nb Number of images to write.
+        \param dx Width of images in the written file.
+        \param dy Height of images in the written file.
+        \param dz Depth of images in the written file.
+        \param dc Spectrum of images in the written file.
+    **/
+    static void save_empty_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 dc=1) {
+      return _save_empty_cimg(file,0,nb,dx,dy,dz,dc);
+    }
+
+    //! Save a file in TIFF format.
+    /**
+      \param filename Filename to write data to.
+      \param compression Compression mode used to write data.
+    **/
+    const CImgList<T>& save_tiff(const char *const filename, const unsigned int compression=0) const {
+      if (!filename)
+        throw CImgArgumentException(_cimglist_instance
+                                    "save_tiff() : Specified filename is (null).",
+                                    cimglist_instance);
+      if (is_empty())
+        throw CImgInstanceException(_cimglist_instance
+                                    "save_tiff() : Empty instance, for file '%s'.",
+                                    cimglist_instance,
+                                    filename);
+#ifndef cimg_use_tiff
+      if (_width==1) _data[0].save_tiff(filename,compression);
+      else cimglist_for(*this,l) {
+          char nfilename[1024] = { 0 };
+          cimg::number_filename(filename,l,6,nfilename);
+          _data[l].save_tiff(nfilename,compression);
+        }
+#else
+      TIFF *tif = TIFFOpen(filename,"w");
+      if (tif) {
+        for (unsigned int dir = 0, l = 0; l<_width; ++l) {
+          const CImg<T>& img = (*this)[l];
+          if (img) {
+            if (img._depth==1) img._save_tiff(tif,dir++,compression);
+            else cimg_forZ(img,z) img.get_slice(z)._save_tiff(tif,dir++,compression);
+          }
+        }
+        TIFFClose(tif);
+      } else
+        throw CImgIOException(_cimglist_instance
+                              "save_tiff() : Failed to open stream for file '%s'.",
+                              cimglist_instance,
+                              filename);
+#endif
+      return *this;
+    }
+
+
+    //! Save a list as a gzipped file, using external tool 'gzip'.
+    /**
+      \param filename Filename to write data to.
+    **/
+    const CImgList<T>& save_gzip_external(const char *const filename) const {
+      if (!filename)
+        throw CImgIOException(_cimglist_instance
+                              "save_gzip_external() : Specified filename is (null).",
+                              cimglist_instance);
+
+      char command[1024] = { 0 }, filetmp[512] = { 0 }, body[512] = { 0 };
+      const char
+        *ext = cimg::split_filename(filename,body),
+        *ext2 = cimg::split_filename(body,0);
+      std::FILE *file;
+      do {
+        if (!cimg::strcasecmp(ext,"gz")) {
+          if (*ext2) cimg_snprintf(filetmp,sizeof(filetmp),"%s%c%s.%s",cimg::temporary_path(),cimg_file_separator,cimg::filenamerand(),ext2);
+          else cimg_snprintf(filetmp,sizeof(filetmp),"%s%c%s.cimg",cimg::temporary_path(),cimg_file_separator,cimg::filenamerand());
+        } else {
+          if (*ext) cimg_snprintf(filetmp,sizeof(filetmp),"%s%c%s.%s",cimg::temporary_path(),cimg_file_separator,cimg::filenamerand(),ext);
+          else cimg_snprintf(filetmp,sizeof(filetmp),"%s%c%s.cimg",cimg::temporary_path(),cimg_file_separator,cimg::filenamerand());
+        }
+        if ((file=std::fopen(filetmp,"rb"))!=0) cimg::fclose(file);
+      } while (file);
+
+      if (is_saveable(body)) {
+        save(filetmp);
+        cimg_snprintf(command,sizeof(command),"%s -c %s > \"%s\"",cimg::gzip_path(),filetmp,filename);
+        cimg::system(command);
+        file = std::fopen(filename,"rb");
+        if (!file)
+          throw CImgIOException(_cimglist_instance
+                                "save_gzip_external() : Failed to save file '%s' with external command 'gzip'.",
+                                cimglist_instance,
+                                filename);
+        else cimg::fclose(file);
+        std::remove(filetmp);
+      } else {
+        char nfilename[1024] = { 0 };
+        cimglist_for(*this,l) {
+          cimg::number_filename(body,l,6,nfilename);
+          if (*ext) std::sprintf(nfilename + std::strlen(nfilename),".%s",ext);
+          _data[l].save_gzip_external(nfilename);
+        }
+      }
+      return *this;
+    }
+
+    //! Save an image sequence using the external tool 'ffmpeg'.
+    /**
+      \param filename Filename to write data to.
+      \param first_frame Index of first image frame to write.
+      \param last_frame Index of last image frame to write.
+    **/
+    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=0, const unsigned int fps=25, const unsigned int bitrate=2048) const {
+      if (!filename)
+        throw CImgArgumentException(_cimglist_instance
+                                    "save_ffmpeg_external() : Specified filename is (null).",
+                                    cimglist_instance);
+      if (is_empty())
+        throw CImgInstanceException(_cimglist_instance
+                                    "save_ffmpeg_external() : Empty instance, for file '%s'.",
+                                    cimglist_instance,
+                                    filename);
+      const char
+        *const ext = cimg::split_filename(filename),
+        *const _codec = codec?codec:!cimg::strcasecmp(ext,"flv")?"flv":"mpeg2video";
+
+      char command[1024] = { 0 }, filetmp[512] = { 0 }, filetmp2[512] = { 0 };
+      std::FILE *file = 0;
+      const unsigned int nlast_frame = last_frame==~0U?_width-1:last_frame;
+      if (first_frame>=_width || nlast_frame>=_width)
+        throw CImgArgumentException(_cimglist_instance
+                                    "save_ffmpeg_external() : Out of range specified frames [%u,%u] for file '%s'.",
+                                    cimglist_instance,
+                                    filename,first_frame,last_frame);
+
+      for (unsigned int ll = first_frame; ll<=nlast_frame; ++ll) if (!_data[ll].is_sameXYZ(_data[0]))
+        throw CImgInstanceException(_cimglist_instance
+                                    "save_ffmpeg_external() : Invalid instance dimensions for file '%s'.",
+                                    cimglist_instance,
+                                    filename);
+      do {
+        cimg_snprintf(filetmp,sizeof(filetmp),"%s%c%s",cimg::temporary_path(),cimg_file_separator,cimg::filenamerand());
+        cimg_snprintf(filetmp2,sizeof(filetmp2),"%s_000001.ppm",filetmp);
+        if ((file=std::fopen(filetmp2,"rb"))!=0) cimg::fclose(file);
+      } while (file);
+      for (unsigned int l = first_frame; l<=nlast_frame; ++l) {
+        cimg_snprintf(filetmp2,sizeof(filetmp2),"%s_%.6u.ppm",filetmp,l+1);
+        if (_data[l]._depth>1 || _data[l]._spectrum!=3) _data[l].get_resize(-100,-100,1,3).save_pnm(filetmp2);
+        else _data[l].save_pnm(filetmp2);
+      }
+#if cimg_OS!=2
+      cimg_snprintf(command,sizeof(command),"ffmpeg -i %s_%%6d.ppm -vcodec %s -b %uk -r %u -y \"%s\" >/dev/null 2>&1",filetmp,_codec,bitrate,fps,filename);
+#else
+      cimg_snprintf(command,sizeof(command),"\"ffmpeg -i %s_%%6d.ppm -vcodec %s -b %uk -r %u -y \"%s\"\" >NUL 2>&1",filetmp,_codec,bitrate,fps,filename);
+#endif
+      cimg::system(command);
+      file = std::fopen(filename,"rb");
+      if (!file)
+        throw CImgIOException(_cimglist_instance
+                              "save_ffmpeg_external() : Failed to save file '%s' with external command 'ffmpeg'.",
+                              cimglist_instance,
+                              filename);
+      else cimg::fclose(file);
+      cimglist_for(*this,lll) { cimg_snprintf(filetmp2,sizeof(filetmp2),"%s_%.6u.ppm",filetmp,lll+1); std::remove(filetmp2); }
+      return *this;
+    }
+
+    //@}
+    //----------------------------------
+    //
+    //! \name Others
+    //@{
+    //----------------------------------
+
+    //! Create an auto-cropped font (along the X axis) from a input font.
+    /**
+       \see get_crop_font().
+    **/
+    CImgList<T>& crop_font() {
+      return get_crop_font().move_to(*this);
+    }
+
+    //! Create an auto-cropped font (along the X axis) from a input font \newinstance.
+    /**
+       \see crop_font().
+    **/
+    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) CImg<T>(letter._width,letter._height,1,letter._spectrum,0).move_to(res);
+        else letter.get_crop(xmin,0,xmax,letter._height-1).move_to(res);
+      }
+      res[' '].resize(res['f']._width,-100,-100,-100,0);
+      if (' '+256<res.size()) res[' '+256].resize(res['f']._width,-100,-100,-100,0);
+      return res;
+    }
+
+
+    //! Return a CImg pre-defined font with desired size.
+    /**
+       \param font_height Height of the desired font (exact match for 11,13,17,19,24,32,38,57)
+       \param is_variable_width Decide if the font has a variable (\c true) or fixed (\c false) width.
+    **/
+    static const CImgList<T>& font(const unsigned int font_height, const bool is_variable_width=true) {
+
+#define _cimg_font(sx,sy) \
+      if (!is_variable_width && (!font || font[0]._height!=sy)) font = _font(cimg::font##sx##x##sy,sx,sy,false); \
+      if (is_variable_width  && (!vfont || vfont[0]._height!=sy)) vfont = _font(cimg::font##sx##x##sy,sx,sy,true); \
+      if (font_height==sy) return is_variable_width?vfont:font; \
+      if (is_variable_width) { \
+        if (cvfont && font_height==cvfont[0]._height) return cvfont; \
+        cvfont = vfont; \
+        cimglist_for(cvfont,l) \
+          cvfont[l].resize(cimg::max(1U,cvfont[l]._width*font_height/cvfont[l]._height),font_height,-100,-100, \
+                           cvfont[0]._height>font_height?2:5); \
+        return cvfont; \
+      } else { \
+        if (cfont && font_height==cfont[0]._height) return cfont; \
+        cfont = font; \
+        cimglist_for(cfont,l) \
+          cfont[l].resize(cimg::max(1U,cfont[l]._width*font_height/cfont[l]._height),font_height,-100,-100, \
+                          cfont[0]._height>font_height?2:5); \
+        return cfont; \
+      } \
+
+      static CImgList<T> font, vfont, cfont, cvfont;
+      if (!font_height) return CImgList<T>::empty();
+      if (font_height<=13) { _cimg_font(10,13); } // [1,13] -> ref 13
+      if (font_height<=28) { _cimg_font(12,24); } // [14,28] -> ref 24
+      if (font_height<=32) { _cimg_font(16,32); } // [29,32] -> ref 32
+      _cimg_font(29,57);                          // [33,+inf] -> ref 57
+    }
+
+    static CImgList<T> _font(const unsigned int *const font, const unsigned int w, const unsigned int h, const bool is_variable_width) {
+      CImgList<T> res(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];
+          unsigned int xm = x%w;
+          img(xm,y) = (T)((val&m)?1:0);
+        }
+      if (is_variable_width) res.crop_font();
+      return  res.insert(res);
+    }
+
+    //! Compute a 1d Fast Fourier Transform, along specified axis.
+    /**
+       \param axis Axis along which the Fourier transform is computed.
+       \param invert Flag telling if the direct (\c false) or inverse transform (\c true) is computed.
+    **/
+    CImgList<T>& FFT(const char axis, const bool invert=false) {
+      if (is_empty()) return *this;
+      if (_width==1) insert(1);
+      if (_width>2)
+        cimg::warn(_cimglist_instance
+                   "FFT() : Instance has more than 2 images",
+                   cimglist_instance);
+
+      CImg<T>::FFT(_data[0],_data[1],axis,invert);
+      return *this;
+    }
+
+    //! Compute a 1-D Fast Fourier Transform, along specified axis \newinstance.
+    CImgList<Tfloat> get_FFT(const char axis, const bool invert=false) const {
+      return CImgList<Tfloat>(*this,false).FFT(axis,invert);
+    }
+
+    //! Compute a n-d Fast Fourier Transform.
+    /**
+      \param invert Flag telling if the direct (\c false) or inverse transform (\c true) is computed.
+    **/
+    CImgList<T>& FFT(const bool invert=false) {
+      if (is_empty()) return *this;
+      if (_width==1) insert(1);
+      if (_width>2)
+        cimg::warn(_cimglist_instance
+                   "FFT() : Instance has more than 2 images",
+                   cimglist_instance);
+
+      CImg<T>::FFT(_data[0],_data[1],invert);
+      return *this;
+    }
+
+    //! Compute a n-d Fast Fourier Transform \newinstance.
+    CImgList<Tfloat> get_FFT(const bool invert=false) const {
+      return CImgList<Tfloat>(*this,false).FFT(invert);
+    }
+
+    //! Reverse primitives orientations of a 3d object.
+    /**
+      \see get_reverse_object3d().
+    **/
+    CImgList<T>& reverse_object3d() {
+      cimglist_for(*this,l) {
+        CImg<T>& p = _data[l];
+        switch (p.size()) {
+        case 2: case 3: cimg::swap(p[0],p[1]); break;
+        case 6: cimg::swap(p[0],p[1],p[2],p[4],p[3],p[5]); break;
+        case 9: cimg::swap(p[0],p[1],p[3],p[5],p[4],p[6]); break;
+        case 4: cimg::swap(p[0],p[1],p[2],p[3]); break;
+        case 12: cimg::swap(p[0],p[1],p[2],p[3],p[4],p[6],p[5],p[7],p[8],p[10],p[9],p[11]); break;
+        }
+      }
+      return *this;
+    }
+
+    //! Reverse primitives orientations of a 3d object \newinstance.
+    CImgList<T> get_reverse_object3d() const {
+      return (+*this).reverse_object3d();
+    }
+
+    //@}
+  }; // struct CImgList<T> { ...
+
+  /*
+  #---------------------------------------------
+  #
+   # Completion of previously declared functions
+   #
+   #----------------------------------------------
+  */
+
+namespace cimg {
+
+  //! Display a simple dialog box, and wait for the user's response.
+  /**
+     \param title Title of the dialog window.
+     \param msg Main message displayed inside the dialog window.
+     \param button1_label Label of the 1st button.
+     \param button2_label Label of the 2nd button (\c 0 to hide button).
+     \param button3_label Label of the 3rd button (\c 0 to hide button).
+     \param button4_label Label of the 4th button (\c 0 to hide button).
+     \param button5_label Label of the 5th button (\c 0 to hide button).
+     \param button6_label Label of the 6th button (\c 0 to hide button).
+     \param logo Image logo displayed at the left of the main message (optional).
+     \param centering Flag telling if the dialog window must be centered on the screen.
+     \return Indice of clicked button (from \c 0 to \c 5), or \c -1 if the dialog window has been closed by the user.
+     \note
+     - Up to 6 buttons can be defined in the dialog window.
+     - The function returns when a user clicked one of the button or closed the dialog window.
+     - If a button text is set to 0, the corresponding button (and the followings) will not appear in the dialog box. At least one button must be specified.
+  **/
+  template<typename t>
+  inline int dialog(const char *const title, const char *const msg,
+                    const char *const button1_label, const char *const button2_label,
+                    const char *const button3_label, const char *const button4_label,
+                    const char *const button5_label, const char *const button6_label,
+                    const CImg<t>& logo, const bool is_centered = false) {
+#if cimg_display==0
+    cimg::unused(title,msg,button1_label,button2_label,button3_label,button4_label,button5_label,button6_label,logo._data,is_centered);
+    throw CImgIOException("cimg::dialog() : No display available.");
+#else
+    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_label) { CImg<unsigned char>().draw_text(0,0,button1_label,black,gray,1,13).move_to(buttons);
+      if (button2_label) { CImg<unsigned char>().draw_text(0,0,button2_label,black,gray,1,13).move_to(buttons);
+        if (button3_label) { CImg<unsigned char>().draw_text(0,0,button3_label,black,gray,1,13).move_to(buttons);
+          if (button4_label) { CImg<unsigned char>().draw_text(0,0,button4_label,black,gray,1,13).move_to(buttons);
+            if (button5_label) { CImg<unsigned char>().draw_text(0,0,button5_label,black,gray,1,13).move_to(buttons);
+              if (button6_label) { CImg<unsigned char>().draw_text(0,0,button6_label,black,gray,1,13).move_to(buttons);
+              }}}}}}
+    if (!buttons._width)
+      throw CImgArgumentException("cimg::dialog() : No buttons have been defined.");
+    cimglist_for(buttons,l) buttons[l].resize(-100,-100,1,3);
+
+    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) {
+      CImg<unsigned char>(cbutton).draw_image(1+(bw-buttons[ll].width())/2,1+(bh-buttons[ll].height())/2,buttons[ll]).
+        move_to(cbuttons);
+      CImg<unsigned char>(sbutton).draw_image((bw-buttons[ll].width())/2,(bh-buttons[ll].height())/2,buttons[ll]).
+        move_to(sbuttons);
+      CImg<unsigned char>(button).draw_image((bw-buttons[ll].width())/2,(bh-buttons[ll].height())/2,buttons[ll]).
+        move_to(buttons[ll]);
+    }
+
+    CImg<unsigned char> canvas;
+    if (msg) CImg<unsigned char>().draw_text(0,0,"%s",black,gray,1,13,msg).resize(-100,-100,1,3).move_to(canvas);
+    const unsigned int
+      bwall = (buttons._width-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] = { 0 };
+    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,is_centered?true:false);
+    if (is_centered) disp.move((CImgDisplay::screen_width() - disp.width())/2,
+                             (CImgDisplay::screen_height() - disp.height())/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,false);
+
+      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._width; break;
+        case cimg::keyARROWLEFT :
+        case cimg::keyARROWUP : selected = (selected+buttons._width-1)%buttons._width; break;
+        }
+        disp.set_key();
+        if (selected!=oselected) refresh = true;
+      }
+    }
+    if (!disp) selected = -1;
+    return selected;
+#endif
+  }
+
+  inline int dialog(const char *const title, const char *const msg,
+                    const char *const button1_label, const char *const button2_label, const char *const button3_label,
+                    const char *const button4_label, const char *const button5_label, const char *const button6_label,
+                    const bool is_centered) {
+    return dialog(title,msg,button1_label,button2_label,button3_label,button4_label,button5_label,button6_label,
+                  CImg<unsigned char>::logo40x38(),is_centered);
+  }
+
+  //! Evaluate math expression.
+  /**
+     \param expression C-string containing the formula to evaluate.
+     \param x Value of a pre-defined variable \c x.
+     \param y Value of a pre-defined variable \c y.
+     \param z Value of a pre-defined variable \c z.
+     \param c Value of a pre-defined variable \c c.
+     \return Result of the formula evaluation.
+     \note Set \c expression to \c 0 to evaluate the latest specified \c expression.
+     \par Example
+     \code
+     const double
+       res1 = cimg::eval("cos(x)^2+sin(y)^2",2,2),  // will return '1'.
+       res2 = cimg::eval(0,1,1);                    // will return '1' too.
+     \endcode
+  **/
+  inline double eval(const char *const expression, const double x, const double y, const double z, const double c) {
+    static const CImg<float> empty;
+    return empty.eval(expression,x,y,z,c);
+  }
+
+  // End of cimg:: namespace
+}
+
+  // End of cimg_library:: namespace
+}
+
+//! Short alias name.
+namespace cil = cimg_library_suffixed;
+
+#ifdef _cimg_redefine_False
+#define False 0
+#endif
+#ifdef _cimg_redefine_True
+#define True 1
+#endif
+#ifdef _cimg_redefine_None
+#define None 0
+#endif
+#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
+#ifdef _cimg_redefine_PI
+#define PI 3.141592653589793238462643383
+#endif
+
+#endif
+// Local Variables:
+// mode: c++
+// End:
new file mode 100644
--- /dev/null
+++ b/Complex_Polynomial.h
@@ -0,0 +1,57 @@
+#ifndef GUARD_Complex_Polynomial
+#define GUARD_Complex_Polynomial
+
+// Complex_Polynomial.h header file
+// Licensed under GPLv2
+// Terms available at http://www.gnu.org/licenses/gpl-2.0.txt
+
+#include <vector>
+#include <complex>
+
+// Complex_Polynomial stores monic polynomials based on their roots, for ease of computation. This does limit the ability of the code to generate fractals with more interesting attributes, but in this context I felt that that limitation was worthwhile. After all, this isn't being done in a CAS where factoring is an option (unlike most of the Mathematica code I've seen for this.)
+
+struct Complex_Polynomial {
+  std::vector<std::complex<double> > roots;
+
+  std::complex<double> evalPt( std::complex<double> pt ) {
+    std::vector<std::complex<double> >::size_type i = 0; //Gotta make sure I evaluate the whole polynomial
+    std::complex<double> val ( 1 , 1 ); //Sensible starting solution
+    for ( i = 0 ; i < roots.size() ; i++ ) {
+      //Multiplying each factor by the result so far
+      val *= ( pt - roots[i] );
+    }
+    return val;
+  }
+  
+  std::complex<double> derivEval( std::complex<double> pt ) {
+    std::vector<std::complex<double> >::size_type i,j,k; //I'll need two indices to track which I'm evaluating and which I'm avoiding.
+    if ( roots.size() == 0 ) {
+      return std::complex<double> ( 0 , 0 );
+    }
+    std::vector<std::complex<double> > stages (roots.size()); //This is the first stage of derivative evaluation.
+    // Since the derivative of a polynomial by its roots is defined by the sum of the polynomials formed by each set of roots less one, this is the fastest route with my arrangement.
+    for ( k = 0 ; k < stages.size() ; k++ ) {
+      stages[k] = std::complex<double> ( 1 , 1 ) ; // Filling all the roots with initial solutions for multiplication.
+    }
+    // Here's the implementation, and it relies on skipping certain elements.
+    // This works by evaluating every factor but the ith, and inputting that into the ith position of the stages vector.
+    for ( i = 0 ; i < roots.size() ; i ++ ) {
+      for ( j = 0 ; j < roots.size() ; j++ ) {
+	if ( j != i ) {
+	  stages[i] *= ( pt - roots[j] ) ;
+	}
+      }
+    }
+    // Now that all the evaluations have occurred, the summation can happen!
+    std::complex<double> result ( 0 , 0 );
+    for ( k = 0 ; k < stages.size() ; k++ ) {
+      result += stages[k]; //Sums all the components of the derivative at the value.
+    }
+    return result;
+  }
+};
+
+std::complex<double> evalPt( std::complex<double> pt );
+std::complex<double> derivEval( std::complex<double> pt );
+
+#endif
new file mode 100644
--- /dev/null
+++ b/Newton Fractal.cpp	
@@ -0,0 +1,108 @@
+// Newton Fractal.cpp
+// Licensed under GPLv2
+// Terms available at http://www.gnu.org/licenses/gpl-2.0.txt
+
+#include <vector>
+#include <complex>
+#include "Cimg.h"
+#include "Complex_Polynomial.h"
+#include <stdexcept>
+
+using namespace std;
+using namespace cimg_library;
+
+complex<double> newton ( complex<double> pt, Complex_Polynomial& fn );
+CImg<unsigned char> plot ( Complex_Polynomial& fn , int imgx , int imgy, double xmin , double xmax, double ymin, double ymax );
+
+
+//First matter is to set up the actual Newton's method. This is fairly straight-forward.
+complex<double> newton ( complex<double> pt, Complex_Polynomial& fn ) {
+  //Finds the derivative, checks for division by zero, and otherwise applies Newton Iteration at the current point.
+  complex<double> valDeriv = fn.derivEval(pt);
+  if ( abs(valDeriv) == 0 )
+    throw domain_error("Divides by zero");
+  return ( pt - ( fn.evalPt(pt) / valDeriv ) );
+}
+
+/*The key is to load each point by a pair of recursive for-loops and then
+iterate the newton function on it 256 times, while tracking the difference between the points. If the real difference or the imaginary difference is larger than epsilon, consider it to not converge. (By 256 iterations it should have reached 0 anyways).
+Run the code in a try-catch block to make sure if at any point it attempts division by zero, the point is immediately marked black.
+*/
+
+CImg<unsigned char> plot ( Complex_Polynomial& fn , int imgx , int imgy, double xmin , double xmax, double ymin, double ymax ) {
+  //Create an imgx * imgy image
+  CImg<unsigned char> img (imgx , imgy , 1 , 3 );
+  
+  double xstep = ( xmax - xmin ) / imgx ;
+  double ystep = ( ymax - ymin ) / imgy ;
+  double delta = pow(10.0,-8);
+  // Colours are in HSV, starting with a random number from 0 to 2pi
+  // Divide circle into n even pieces, match one to each, define an HSV point for each root.
+  int dist_around_circle = 360 / fn.roots.size() ; //Finding how far apart each colour is.
+  int colours [fn.roots.size()][3];
+  int base_colour = ( rand() % 359 ); //Randomly determined base colour.
+  for ( vector<complex<double> >::size_type i = 0 ; i < fn.roots.size() ; i++ ) {
+    colours[i][0] = ( base_colour + i * dist_around_circle ) % 360;
+    colours[i][1] = 1;
+    colours[i][2] = 1;
+    // These are defined in HSV; the first is mod 360 so no problems come about, while the second two are 1 so everything's max saturation and value.
+  }
+
+  // This is a supplied iterator that operates over all the y values in img, very handy.
+  cimg_forY(img,y) {
+    // Because (0,0) is in the upper-left corner, a repositioning was necessary. This translates y values into y coordinates.
+    double ypos = ymax - ( y * ystep );
+    // An iterator on x and also some code to translate x-value to x-coordinate.
+    cimg_forX(img,x){
+      double xpos = xmin + ( x * xstep );
+      //Establishes the first point to test for iteration
+      complex<double> curPt ( xpos , ypos );
+      try{
+	int i = 0;
+	bool rootFound = false; //For each point, check if the root is found
+	while ( i < 256 && !rootFound ) {
+	  for ( vector<complex<double> >::size_type j = 0 ; j < fn.roots.size() ; j++ ) {
+	    if ( abs( curPt - fn.roots[j] ) < delta ) {
+	      //Paint it the colour of that root
+	      img.fillC(x,y,0,colours[j][0], colours[j][1] , colours[j][2] ); //this uses a poorly-documented part of the code.
+	      //There was a reference to that line of code somewhere in the CImg docs, but I forget where.
+	      rootFound = true;
+	      break; //Short-circuits paired with the immediate next line after the for-block
+	    }
+	  }
+	  curPt = newton( curPt, fn ); //Runs the Newton iteration.
+	  i++; //I used a while loop so I could check against the root being found; a for-loop would have done too.
+	}
+      } catch ( domain_error e ) {
+	//This case applies when division by zero is attempted in the derivative
+	//and shouldn't show up too often.
+	//In fact, nothing need happen, because it'll just stay a black pixel.
+	//Which is my plan.
+      }
+
+    }
+  }
+  return img;
+}
+
+int main() {
+
+  //Initialising the random number generator:
+  srand( time(NULL));
+  Complex_Polynomial test;
+  //I should assemble a better way of inputting the polynomial, but that would require monkeying with stdin.
+  //Roots do not NEED to come in pairs, the code will run no matter what.
+  //Simply push how many roots you want into the vector, and run the code. The window can also have its size and scale changed; clearly, a larger window takes longer to map.
+  test.roots.push_back(complex<double> ( -1 , 0 ));
+  test.roots.push_back(complex<double> ( 1 , 0 ) ) ;
+  test.roots.push_back(complex<double> ( 0 , 1 ) );
+  test.roots.push_back(complex<double> ( 0 , - 1 ) );
+  CImg<unsigned char> fractal =  plot(test, 400 , 400 , -2.0 , 2.0 , -2.0 , 2.0);
+  CImgDisplay main_disp(fractal.HSVtoRGB(),"Fractal");
+  while ( !main_disp.is_closed() ){
+    main_disp.wait();
+    if (main_disp.button() ) break;
+  }
+  return 0;
+  
+}