/*
 #
 #  File        : CImg.h
 #                ( C++ header file )
 #
 #  Description : The C++ Template Image Processing Library.
 #                This file is a part of the CImg Library project.
 #                ( http://cimg.sourceforge.net )
 #
 #  Copyright   : David Tschumperle
 #                ( http://www.greyc.ensicaen.fr/~dtschump/ )
 #
 #  Note        : This file is "dual-licensed", you have to choose one
 #                of the two licenses below to apply on this file.
 #
 #  Licenses    : 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.
 #
*/
#ifndef cimg_version
#define cimg_version 129

/*-------------------------------------------------------------
 #
 # Set CImg configuration flags and include required headers
 #
 # If compilation flags are not adapted to your system,
 # you may override their values, before including
 # the header file "CImg.h" (use the #define directive).
 #
 --------------------------------------------------------------*/

// Operating system configuration.
//
// Set 'cimg_OS' to : 2 for Microsoft Windows OS.
//                    1 for Unix-like OS (Linux, Solaris, BSD, MacOSX, Irix, ...).
//                    0 for other OS (less dependancies will be considered).
//
// If 'cimg_OS' is not set, auto-detection is tried.
//
#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(__MACOSX__) || defined(__APPLE__) \
 || defined(sgi)        || defined(__sgi) \
 || defined(__CYGWIN__)
#define cimg_OS            1
#ifndef cimg_display_type
#define cimg_display_type  1
#endif
#ifndef cimg_color_terminal
#define cimg_color_terminal
#endif
#elif defined(_MSC_VER) || defined(WIN32)  || defined(_WIN32) || defined(__WIN32__) \
   || defined(WIN64)    || defined(_WIN64) || defined(__WIN64__)
#define cimg_OS            2
#ifndef cimg_display_type
#define cimg_display_type  2
#endif
#else
#define cimg_OS            0
#ifndef cimg_display_type
#define cimg_display_type  0
#endif
#endif
#endif

// Debug configuration.
//
// Set 'cimg_debug' to : 0 to remove debug messages (exceptions are still thrown anyway).
//                       1 to display debug messages on standard error output (console).
//                       2 to display debug messages in modal windows (default value).
//                       3 to do as 2 + add extra memory access warnings (may slow down the code).
//
#ifndef cimg_debug
#define cimg_debug         2
#endif

// Compiler configuration.
//
// Try to auto-detect Microsoft VC++ 6.0 compiler.
// (Lot of workarounds are needed afterwards to make CImg working with this lame compiler).
//
#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
#if _MSC_VER<1300
#define cimg_use_visualcpp6
#define _WIN32_WINNT 0x0500
#endif
#endif

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

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

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

// OpenMP (http://www.openmp.org)
// Define 'cimg_use_openmp' to enable OpenMP support in CImg.
#ifdef cimg_use_openmp
#include "omp.h"
#endif

// LibPNG (http://www.libpng.org)
// Define 'cimg_use_png' to enable LibPNG support in CImg.
#ifdef cimg_use_png
extern "C" {
#include "png.h"
}
#endif

// LibJPEG (http://en.wikipedia.org/wiki/Libjpeg)
// Define 'cimg_use_jpeg' to enable LibJPEG support in CImg.
#ifdef cimg_use_jpeg
extern "C" {
#include "jpeglib.h"
}
#endif

// LibTIFF (http://www.libtiff.org)
// Define 'cimg_use_tiff' to enable LibTIFF support in CImg.
#ifdef cimg_use_tiff
extern "C" {
#include "tiffio.h"
}
#endif

// FFMPEG av's libs (http://www.ffmpeg.org)
// Define 'cimg_use_ffmpeg' to enable FFMPEG lib support in CImg.
#ifdef cimg_use_ffmpeg
extern "C" {
#include "ffmpeg/avformat.h"
#include "ffmpeg/avcodec.h"
#include "ffmpeg/swscale.h"
}
#endif

// Magick++ (http://www.imagemagick.org/Magick++)
// Define 'cimg_use_magick' to enable Magick++ support in CImg.
#ifdef cimg_use_magick
#include "Magick++.h"
#endif

// FFTW3 (http://www.fftw.org)
// Define 'cimg_use_fftx3' to enable libFFTW3 support in CImg.
#ifdef cimg_use_fftw3
extern "C" {
#include "fftw3.h"
}
#endif

// Lapack (http://www.netlib.org/lapack)
// Define 'cimg_use_lapack' to enable LAPACK support in CImg.
#ifdef cimg_use_lapack
extern "C" {
  extern void sgetrf_(int*, int*, float*, int*, int*, int*);
  extern void sgetri_(int*, float*, int*, int*, float*, int*, int*);
  extern void sgetrs_(char*, int*, int*, float*, int*, int*, float*, int*, int*);
  extern void sgesvd_(char*, char*, int*, int*, float*, int*, float*, float*, int*, float*, int*, float*, int*, int*);
  extern void ssyev_(char*, char*, int*, float*, int*, float*, float*, int*, int*);
  extern void dgetrf_(int*, int*, double*, int*, int*, int*);
  extern void dgetri_(int*, double*, int*, int*, double*, int*, int*);
  extern void dgetrs_(char*, int*, int*, double*, int*, int*, double*, int*, int*);
  extern void dgesvd_(char*, char*, int*, int*, double*, int*, double*, double*, int*, double*, int*, double*, int*, int*);
  extern void dsyev_(char*, char*, int*, double*, int*, double*, double*, int*, int*);
}
#endif

// Check if min/max macros are defined.
//
// CImg does not compile if macros 'min' or 'max' are defined,
// so it '#undef' them 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

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

/*-------------------------------------------------------------------------------------
  #
  #
  # Define some useful macros. Macros of the CImg Library are prefixed by 'cimg_'
  # Documented macros below may be safely used in your own code
  # (particularly useful for option parsing, pointwise and neighbor-based image loops).
  #
  #
  ------------------------------------------------------------------------------------*/

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

// Macros used for neighborhood definitions and manipulations.
// (see module 'Using Image Loops' in the generated documentation).
#define CImg_2x2(I,T) T I[4]; \
                      T& I##cc = I[0]; T& I##nc = I[1]; \
                      T& I##cn = I[2]; T& I##nn = I[3]; \
                      I##cc = I##nc = \
                      I##cn = I##nn = 0

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


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

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

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

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

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

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

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

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

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

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

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

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

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

// Macros used to define special image loops.
// (see section 'Using Image Loops' in the generated documentation for more details).
// (it may look ugly, but it is actually ..sooooooo... useful !)
#define cimg_for(img,ptr,T_ptr) for (T_ptr *ptr = (img).data + (img).size(); (ptr--)>(img).data; )
#define cimg_foroff(img,off) for (unsigned int off = 0; off<(img).size(); ++off)
#define cimglist_for(list,l) for (unsigned int l=0; l<(list).size; ++l)
#define cimglist_apply(list,fn) cimglist_for(list,__##fn) (list)[__##fn].fn

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

#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); \
     }}

#define _CImgList_stdarg(t) \
   {  assign(n,width,height,depth,dim); \
      const unsigned int siz = width*height*depth*dim, nsiz = siz*n; \
      T *ptrd = data->data; \
      va_list ap; \
      va_start(ap,val1); \
      for (unsigned int l=0, s=0, i=0; i<nsiz; ++i) { \
        *(ptrd++) = (T)(i==0?val0:(i==1?val1:va_arg(ap,t))); \
        if ((++s)==siz) { ptrd = data[++l].data; s=0; }\
      } \
      va_end(ap); \
   }

/*------------------------------------------------
 #
 #
 #  Definition of the cimg_library:: namespace
 #
 #
 -------------------------------------------------*/

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

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

  template<typename T=float> struct CImgList;

  struct CImgDisplay;

  struct CImgException;

  namespace cimg {

    // The bodies of the functions below will defined afterwards (at the end of file).
    inline void info();

    inline unsigned int& exception_mode();

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

    template<typename tfunc, typename tp, typename tf>
    inline void marching_cubes(const tfunc& func, const float isovalue,
                               const float x0, const float y0, const float z0,
                               const float x1, const float y1, const float z1,
                               const float resx, const float resy, const float resz,
                               CImgList<tp>& points, CImgList<tf>& primitives,
                               const bool invert_faces=false);

    template<typename tfunc, typename tp, typename tf>
    inline void marching_squares(const tfunc& func, const float isovalue,
                                 const float x0, const float y0,
                                 const float x1, const float y1,
                                 const float resx, const float resy,
                                 CImgList<tp>& points, CImgList<tf>& primitives);
  }

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

  // Please don't use the following macro in your own code !
#define cimg_exception_err(etype,disp_flag) \
  std::va_list ap; \
  va_start(ap,format); \
  std::vsprintf(message,format,ap); \
  va_end(ap); \
  if (cimg::exception_mode()>=1) { \
    if (cimg::exception_mode()>=2 && disp_flag) { \
      try { cimg::dialog(etype,message,"Abort"); } \
      catch (CImgException&) { std::fprintf(stderr,"\n# %s :\n%s\n\n",etype,message); } \
    } else std::fprintf(stderr,"\n# %s :\n%s\n\n",etype,message); \
  } \
  if (cimg::exception_mode()>=3) cimg_library::cimg::info(); \

  //! Class which is thrown when an error occured during a %CImg library function call.
  /**

      \section ex1 Overview

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

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

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

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

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

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

      \section ex2 Exception handling

      When an error occurs, the %CImg Library first displays the error in a modal window.
      Then, it throws an instance of the corresponding exception class, generally leading the program to stop
      (this is the default behavior).
      You can bypass this default behavior by handling the exceptions yourself,
      using a code block <tt>try { ... } catch() { ... }</tt>.
      In this case, you can avoid the apparition of the modal window, by
      defining the environment variable <tt>cimg_debug</tt> to 0 before including the %CImg header file.
      The example below shows how to cleanly handle %CImg Library exceptions :
      \code
      #define cimg_debug 0     // Disable modal window in CImg exceptions.
      #define "CImg.h"
      int main() {
        try {
          ...; // Here, do what you want.
        }
        catch (CImgInstanceException &e) {
          std::fprintf(stderr,"CImg Library Error : %s",e.message);  // Display your own error message
          ...                                                        // Do what you want now.
        }
      }
      \endcode
  **/
  struct CImgException {
    char message[1024]; //!< Message associated with the error that thrown the exception.
    CImgException() { message[0]='\0'; }
    CImgException(const char *format, ...) { cimg_exception_err("CImgException",true); }
  };

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

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

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

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

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

  /*-------------------------------------
   #
   #
   # Definition of the namespace 'cimg'
   #
   #
   --------------------------------------*/

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    template<typename T, typename t> struct superset { typedef T type; };
    template<> struct superset<bool,unsigned char> { typedef unsigned char type; };
    template<> struct superset<bool,char> { typedef char type; };
    template<> struct superset<bool,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,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<char,unsigned 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,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,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,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 internal library variables.
#if cimg_display_type==1
    struct X11info {
      volatile unsigned int nb_wins;
      pthread_t*       event_thread;
      CImgDisplay*     wins[1024];
      Display*         display;
      unsigned int     nb_bits;
      GC*              gc;
      bool             blue_first;
      bool             byte_order;
      bool             shm_enabled;
#ifdef cimg_use_xrandr
      XRRScreenSize *resolutions;
      Rotation curr_rotation;
      unsigned int curr_resolution;
      unsigned int nb_resolutions;
#endif
      X11info():nb_wins(0),event_thread(0),display(0),
                nb_bits(0),gc(0),blue_first(false),byte_order(false),shm_enabled(false) {
#ifdef cimg_use_xrandr
        resolutions = 0;
        curr_rotation = 0;
        curr_resolution = nb_resolutions = 0;
#endif
      }
    };
#if defined(cimg_module)
    X11info& X11attr();
#elif defined(cimg_main)
    X11info& X11attr() { static X11info val; return val; }
#else
    inline X11info& X11attr() { static X11info val; return val; }
#endif

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

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

    inline unsigned int& exception_mode() { static unsigned int mode = cimg_debug; return mode; }

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

#if cimg_display_type==1
    // Keycodes for X11-based graphical systems
    const unsigned int keyESC        = XK_Escape;
    const unsigned int keyF1         = XK_F1;
    const unsigned int keyF2         = XK_F2;
    const unsigned int keyF3         = XK_F3;
    const unsigned int keyF4         = XK_F4;
    const unsigned int keyF5         = XK_F5;
    const unsigned int keyF6         = XK_F6;
    const unsigned int keyF7         = XK_F7;
    const unsigned int keyF8         = XK_F8;
    const unsigned int keyF9         = XK_F9;
    const unsigned int keyF10        = XK_F10;
    const unsigned int keyF11        = XK_F11;
    const unsigned int keyF12        = XK_F12;
    const unsigned int keyPAUSE      = XK_Pause;
    const unsigned int key1          = XK_1;
    const unsigned int key2          = XK_2;
    const unsigned int key3          = XK_3;
    const unsigned int key4          = XK_4;
    const unsigned int key5          = XK_5;
    const unsigned int key6          = XK_6;
    const unsigned int key7          = XK_7;
    const unsigned int key8          = XK_8;
    const unsigned int key9          = XK_9;
    const unsigned int key0          = XK_0;
    const unsigned int keyBACKSPACE  = XK_BackSpace;
    const unsigned int keyINSERT     = XK_Insert;
    const unsigned int keyHOME       = XK_Home;
    const unsigned int keyPAGEUP     = XK_Page_Up;
    const unsigned int keyTAB        = XK_Tab;
    const unsigned int keyQ          = XK_q;
    const unsigned int keyW          = XK_w;
    const unsigned int keyE          = XK_e;
    const unsigned int keyR          = XK_r;
    const unsigned int keyT          = XK_t;
    const unsigned int keyY          = XK_y;
    const unsigned int keyU          = XK_u;
    const unsigned int keyI          = XK_i;
    const unsigned int keyO          = XK_o;
    const unsigned int keyP          = XK_p;
    const unsigned int keyDELETE     = XK_Delete;
    const unsigned int keyEND        = XK_End;
    const unsigned int keyPAGEDOWN   = XK_Page_Down;
    const unsigned int keyCAPSLOCK   = XK_Caps_Lock;
    const unsigned int keyA          = XK_a;
    const unsigned int keyS          = XK_s;
    const unsigned int keyD          = XK_d;
    const unsigned int keyF          = XK_f;
    const unsigned int keyG          = XK_g;
    const unsigned int keyH          = XK_h;
    const unsigned int keyJ          = XK_j;
    const unsigned int keyK          = XK_k;
    const unsigned int keyL          = XK_l;
    const unsigned int keyENTER      = XK_Return;
    const unsigned int keySHIFTLEFT  = XK_Shift_L;
    const unsigned int keyZ          = XK_z;
    const unsigned int keyX          = XK_x;
    const unsigned int keyC          = XK_c;
    const unsigned int keyV          = XK_v;
    const unsigned int keyB          = XK_b;
    const unsigned int keyN          = XK_n;
    const unsigned int keyM          = XK_m;
    const unsigned int keySHIFTRIGHT = XK_Shift_R;
    const unsigned int keyARROWUP    = XK_Up;
    const unsigned int keyCTRLLEFT   = XK_Control_L;
    const unsigned int keyAPPLEFT    = XK_Super_L;
    const unsigned int 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_type==2
    // Keycodes for Windows
    const unsigned int keyESC        = VK_ESCAPE;
    const unsigned int keyF1         = VK_F1;
    const unsigned int keyF2         = VK_F2;
    const unsigned int keyF3         = VK_F3;
    const unsigned int keyF4         = VK_F4;
    const unsigned int keyF5         = VK_F5;
    const unsigned int keyF6         = VK_F6;
    const unsigned int keyF7         = VK_F7;
    const unsigned int keyF8         = VK_F8;
    const unsigned int keyF9         = VK_F9;
    const unsigned int keyF10        = VK_F10;
    const unsigned int keyF11        = VK_F11;
    const unsigned int keyF12        = VK_F12;
    const unsigned int keyPAUSE      = VK_PAUSE;
    const unsigned int key1          = '1';
    const unsigned int key2          = '2';
    const unsigned int key3          = '3';
    const unsigned int key4          = '4';
    const unsigned int key5          = '5';
    const unsigned int key6          = '6';
    const unsigned int key7          = '7';
    const unsigned int key8          = '8';
    const unsigned int key9          = '9';
    const unsigned int key0          = '0';
    const unsigned int keyBACKSPACE  = VK_BACK;
    const unsigned int keyINSERT     = VK_INSERT;
    const unsigned int keyHOME       = VK_HOME;
    const unsigned int keyPAGEUP     = VK_PRIOR;
    const unsigned int keyTAB        = VK_TAB;
    const unsigned int keyQ          = 'Q';
    const unsigned int keyW          = 'W';
    const unsigned int keyE          = 'E';
    const unsigned int keyR          = 'R';
    const unsigned int keyT          = 'T';
    const unsigned int keyY          = 'Y';
    const unsigned int keyU          = 'U';
    const unsigned int keyI          = 'I';
    const unsigned int keyO          = 'O';
    const unsigned int keyP          = 'P';
    const unsigned int keyDELETE     = VK_DELETE;
    const unsigned int keyEND        = VK_END;
    const unsigned int keyPAGEDOWN   = VK_NEXT;
    const unsigned int keyCAPSLOCK   = VK_CAPITAL;
    const unsigned int keyA          = 'A';
    const unsigned int keyS          = 'S';
    const unsigned int keyD          = 'D';
    const unsigned int keyF          = 'F';
    const unsigned int keyG          = 'G';
    const unsigned int keyH          = 'H';
    const unsigned int keyJ          = 'J';
    const unsigned int keyK          = 'K';
    const unsigned int keyL          = 'L';
    const unsigned int keyENTER      = VK_RETURN;
    const unsigned int keySHIFTLEFT  = VK_SHIFT;
    const unsigned int keyZ          = 'Z';
    const unsigned int keyX          = 'X';
    const unsigned int keyC          = 'C';
    const unsigned int keyV          = 'V';
    const unsigned int keyB          = 'B';
    const unsigned int keyN          = 'N';
    const unsigned int keyM          = 'M';
    const unsigned int keySHIFTRIGHT = VK_SHIFT;
    const unsigned int keyARROWUP    = VK_UP;
    const unsigned int keyCTRLLEFT   = VK_CONTROL;
    const unsigned int keyAPPLEFT    = VK_LWIN;
    const unsigned int keySPACE      = VK_SPACE;
    const unsigned int keyALTGR      = VK_CONTROL;
    const unsigned int keyAPPRIGHT   = VK_RWIN;
    const unsigned int keyMENU       = VK_APPS;
    const unsigned int keyCTRLRIGHT  = VK_CONTROL;
    const unsigned int keyARROWLEFT  = VK_LEFT;
    const unsigned int keyARROWDOWN  = VK_DOWN;
    const unsigned int keyARROWRIGHT = VK_RIGHT;
    const unsigned int keyPAD0       = 0x60;
    const unsigned int keyPAD1       = 0x61;
    const unsigned int keyPAD2       = 0x62;
    const unsigned int keyPAD3       = 0x63;
    const unsigned int keyPAD4       = 0x64;
    const unsigned int keyPAD5       = 0x65;
    const unsigned int keyPAD6       = 0x66;
    const unsigned int keyPAD7       = 0x67;
    const unsigned int keyPAD8       = 0x68;
    const unsigned int keyPAD9       = 0x69;
    const unsigned int keyPADADD     = VK_ADD;
    const unsigned int keyPADSUB     = VK_SUBTRACT;
    const unsigned int keyPADMUL     = VK_MULTIPLY;
    const unsigned int keyPADDIV     = VK_DIVIDE;

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

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

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

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

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

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

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

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

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

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

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

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

    // Display a warning message.
    inline void warn(const char *format, ...) {
      if (cimg::exception_mode()>=1) {
        char message[8192];
        std::va_list ap;
        va_start(ap,format);
        std::vsprintf(message,format,ap);
        va_end(ap);
#ifdef cimg_strict_warnings
        throw CImgWarningException(message);
#else
        std::fprintf(stderr,"\n<CImg Warning> %s\n",message);
#endif
      }
    }

    // Compute '1+log10(x)'.
    inline int xln(const int x) {
      return x>0?(int)(1+std::log10((double)x)):1;
    }

    // 'Uncase' an ascii character.
    inline char uncase(const char x) {
      return (char)((x<'A'||x>'Z')?x:x-'A'+'a');
    }

    // Convert a string to a float, allowing fractions as '1/2'.
    inline float atof(const char *str) {
      float x = 0,y = 1;
      if (!str) return 0; else { std::sscanf(str,"%g/%g",&x,&y); return x/y; }
    }

    // Compute length of a string.
    inline int strlen(const char *s) {
      if (s) { int k; for (k = 0; s[k]; ++k) ; return k; }
      return -1;
    }

    // Compare two strings together.
    inline int strncmp(const char *s1, const char *s2, const int l) {
      if (s1 && s2) { int n = 0; for (int k = 0; k<l; ++k) n+=std::abs(s1[k] - s2[k]); return n; }
      return 0;
    }

    // Compare two strings together.
    inline int strncasecmp(const char *s1, const char *s2, const int l) {
      if (s1 && s2) { int n = 0; for (int k = 0; k<l; ++k) n+=std::abs(uncase(s1[k])-uncase(s2[k])); return n; }
      return 0;
    }

    // Compare two strings together.
    inline int strcmp(const char *s1, const char *s2) {
      const int l1 = cimg::strlen(s1), l2 = cimg::strlen(s2);
      return cimg::strncmp(s1,s2,1+(l1<l2?l1:l2));
    }

    // Compare two strings together.
    inline int strcasecmp(const char *s1, const char *s2) {
      const int l1 = cimg::strlen(s1), l2 = cimg::strlen(s2);
      return cimg::strncasecmp(s1,s2,1+(l1<l2?l1:l2));
    }

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

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

    //! Return \c false for little endian CPUs (Intel), \c true for big endian CPUs (Motorola).
    inline bool endian() {
      const int x=1;
      return ((unsigned char*)&x)[0]?false:true;
    }

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

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

    inline unsigned int 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 certain number of milliseconds since the last call.
    /**
       This function is equivalent to sleep() but the waiting time is computed with regard to the last call
       of wait(). It may be used to temporize your program properly.
       \sa sleep(), time().
    **/
    inline unsigned int wait(const unsigned int milliseconds) {
      static unsigned long timer = 0;
      if (!timer) timer = cimg::time();
      return wait(milliseconds,timer);
    }

    // Use a specific srand initialization to avoid multi-threading problems
    // (executed only one time for a single program).
    inline void srand() {
      static bool first_time = true;
      if (first_time) {
        std::srand(cimg::time());
        unsigned char *const rand_mem = new unsigned char[1+std::rand()%2048];
        std::srand((unsigned int)(std::rand() + (unsigned long)rand_mem));
        delete[] rand_mem;
        first_time = false;
      }
    }

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

    inline void system(const char *command, const char *module_name=0) {
      int ret;
#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;
      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);
		ret = 0;
      } else
#endif
      ret = std::system(command);
      command = module_name = (const char *)ret;
    }

    // Convert filename into windows-style (without spaces)
#if cimg_OS==2
    inline void winformat_string(char *const s) {
      if (s && s[0]) {
        char *const ns = new char[MAX_PATH];
        if (GetShortPathNameA(s,ns,MAX_PATH)) std::strcpy(s,ns);
      }
    }
#else
    inline void winformat_string(char *const) {}
#endif

    // Return path to the "Program files/" directory (windows only).
    /**
       This function is used internally in imagemagick_path,
       graphicsmagick_path and medcon_path on Windows platforms
    **/
#if cimg_OS==2
    inline const char* programfiles_path() {
      static char *st_programfiles_path = 0;
      if (!st_programfiles_path) {
        st_programfiles_path = new char[MAX_PATH];
        std::memset(st_programfiles_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_programfiles_path,0x0026,false)) {
          const char *pfPath = getenv("PROGRAMFILES");
          if (pfPath) std::strcpy(st_programfiles_path,pfPath);
          else std::strcpy(st_programfiles_path,"C:\\PROGRA~1");
        }
#else
        std::strcpy(st_programfiles_path,"C:\\PROGRA~1");
#endif
      }
      return st_programfiles_path;
    }
#endif

    //! Return path of the ImageMagick's \c convert tool.
    /**
       If you have installed the <a href="http://www.imagemagick.org">ImageMagick package</a>
       in a standard directory, this function should return the correct path of the \c convert tool
       used by the %CImg Library to load and save compressed image formats.
       Conversely, if the \c convert executable is not auto-detected by the function,
       you can define the macro \c cimg_imagemagick_path with the correct path
       of the \c convert executable, before including <tt>CImg.h</tt> in your program :
       \code
       #define cimg_imagemagick_path "/users/thatsme/local/bin/convert"
       #include "CImg.h"

       int main() {
         CImg<> img("my_image.jpg");     // Read a JPEG image file.
         return 0;
       }
       \endcode

       Note that non compressed image formats can be read without installing ImageMagick.

       \sa temporary_path(), get_load_imagemagick_external(), load_imagemagick_external(), save_imagemagick_external().
    **/
    inline const char* imagemagick_path() {
      static char *st_imagemagick_path = 0;
      if (!st_imagemagick_path) {
        st_imagemagick_path = new char[1024];
        std::memset(st_imagemagick_path,0,1024);
        bool path_found = false;
        std::FILE *file = 0;
#ifdef cimg_imagemagick_path
        std::strncpy(st_imagemagick_path,cimg_imagemagick_path,1023);
        if ((file=std::fopen(st_imagemagick_path,"r"))!=0) { std::fclose(file); path_found = true; }
#endif
#if cimg_OS==2
        const char *pf_path = programfiles_path();
        if (!path_found) {
          std::sprintf(st_imagemagick_path,".\\convert.exe");
          if ((file=std::fopen(st_imagemagick_path,"r"))!=0) { std::fclose(file); path_found = true; }
        }
        { for (int k=32; k>=10 && !path_found; --k) {
          std::sprintf(st_imagemagick_path,"%s\\IMAGEM~1.%.2d-\\convert.exe",pf_path,k);
          if ((file=std::fopen(st_imagemagick_path,"r"))!=0) { std::fclose(file); path_found = true; }
        }}
        { for (int k=9; k>=0 && !path_found; --k) {
          std::sprintf(st_imagemagick_path,"%s\\IMAGEM~1.%d-Q\\convert.exe",pf_path,k);
          if ((file=std::fopen(st_imagemagick_path,"r"))!=0) { std::fclose(file); path_found = true; }
        }}
        { for (int k=32; k>=0 && !path_found; --k) {
          std::sprintf(st_imagemagick_path,"%s\\IMAGEM~1.%d\\convert.exe",pf_path,k);
          if ((file=std::fopen(st_imagemagick_path,"r"))!=0) { std::fclose(file); path_found = true; }
        }}
        { for (int k=32; k>=10 && !path_found; --k) {
          std::sprintf(st_imagemagick_path,"%s\\IMAGEM~1.%.2d-\\VISUA~1\\BIN\\convert.exe",pf_path,k);
          if ((file=std::fopen(st_imagemagick_path,"r"))!=0) { std::fclose(file); path_found = true; }
        }}
        { for (int k=9; k>=0 && !path_found; --k) {
          std::sprintf(st_imagemagick_path,"%s\\IMAGEM~1.%d-Q\\VISUA~1\\BIN\\convert.exe",pf_path,k);
          if ((file=std::fopen(st_imagemagick_path,"r"))!=0) { std::fclose(file); path_found = true; }
        }}
        { for (int k=32; k>=0 && !path_found; --k) {
          std::sprintf(st_imagemagick_path,"%s\\IMAGEM~1.%d\\VISUA~1\\BIN\\convert.exe",pf_path,k);
          if ((file=std::fopen(st_imagemagick_path,"r"))!=0) { std::fclose(file); path_found = true; }
        }}
        { for (int k=32; k>=10 && !path_found; --k) {
          std::sprintf(st_imagemagick_path,"C:\\IMAGEM~1.%.2d-\\convert.exe",k);
          if ((file=std::fopen(st_imagemagick_path,"r"))!=0) { std::fclose(file); path_found = true; }
        }}
        { for (int k=9; k>=0 && !path_found; --k) {
          std::sprintf(st_imagemagick_path,"C:\\IMAGEM~1.%d-Q\\convert.exe",k);
          if ((file=std::fopen(st_imagemagick_path,"r"))!=0) { std::fclose(file); path_found = true; }
        }}
        { for (int k=32; k>=0 && !path_found; --k) {
          std::sprintf(st_imagemagick_path,"C:\\IMAGEM~1.%d\\convert.exe",k);
          if ((file=std::fopen(st_imagemagick_path,"r"))!=0) { std::fclose(file); path_found = true; }
        }}
        { for (int k=32; k>=10 && !path_found; --k) {
          std::sprintf(st_imagemagick_path,"C:\\IMAGEM~1.%.2d-\\VISUA~1\\BIN\\convert.exe",k);
          if ((file=std::fopen(st_imagemagick_path,"r"))!=0) { std::fclose(file); path_found = true; }
        }}
        { for (int k=9; k>=0 && !path_found; --k) {
          std::sprintf(st_imagemagick_path,"C:\\IMAGEM~1.%d-Q\\VISUA~1\\BIN\\convert.exe",k);
          if ((file=std::fopen(st_imagemagick_path,"r"))!=0) { std::fclose(file); path_found = true; }
        }}
        { for (int k=32; k>=0 && !path_found; --k) {
          std::sprintf(st_imagemagick_path,"C:\\IMAGEM~1.%d\\VISUA~1\\BIN\\convert.exe",k);
          if ((file=std::fopen(st_imagemagick_path,"r"))!=0) { std::fclose(file); path_found = true; }
        }}
        { for (int k=32; k>=10 && !path_found; --k) {
          std::sprintf(st_imagemagick_path,"D:\\IMAGEM~1.%.2d-\\convert.exe",k);
          if ((file=std::fopen(st_imagemagick_path,"r"))!=0) { std::fclose(file); path_found = true; }
        }}
        { for (int k=9; k>=0 && !path_found; --k) {
          std::sprintf(st_imagemagick_path,"D:\\IMAGEM~1.%d-Q\\convert.exe",k);
          if ((file=std::fopen(st_imagemagick_path,"r"))!=0) { std::fclose(file); path_found = true; }
        }}
        { for (int k=32; k>=0 && !path_found; --k) {
          std::sprintf(st_imagemagick_path,"D:\\IMAGEM~1.%d\\convert.exe",k);
          if ((file=std::fopen(st_imagemagick_path,"r"))!=0) { std::fclose(file); path_found = true; }
        }}
        { for (int k=32; k>=10 && !path_found; --k) {
          std::sprintf(st_imagemagick_path,"D:\\IMAGEM~1.%.2d-\\VISUA~1\\BIN\\convert.exe",k);
          if ((file=std::fopen(st_imagemagick_path,"r"))!=0) { std::fclose(file); path_found = true; }
        }}
        { for (int k=9; k>=0 && !path_found; --k) {
          std::sprintf(st_imagemagick_path,"D:\\IMAGEM~1.%d-Q\\VISUA~1\\BIN\\convert.exe",k);
          if ((file=std::fopen(st_imagemagick_path,"r"))!=0) { std::fclose(file); path_found = true; }
        }}
        { for (int k=32; k>=0 && !path_found; --k) {
          std::sprintf(st_imagemagick_path,"D:\\IMAGEM~1.%d\\VISUA~1\\BIN\\convert.exe",k);
          if ((file=std::fopen(st_imagemagick_path,"r"))!=0) { std::fclose(file); path_found = true; }
        }}
        if (!path_found) std::strcpy(st_imagemagick_path,"convert.exe");
#else
        if (!path_found) {
          std::sprintf(st_imagemagick_path,"./convert");
          if ((file=std::fopen(st_imagemagick_path,"r"))!=0) { std::fclose(file); path_found = true; }
        }
        if (!path_found) std::strcpy(st_imagemagick_path,"convert");
#endif
        winformat_string(st_imagemagick_path);
      }
      return st_imagemagick_path;
    }

    //! Return path of the GraphicsMagick's \c gm tool.
    /**
       If you have installed the <a href="http://www.graphicsmagick.org">GraphicsMagick package</a>
       in a standard directory, this function should return the correct path of the \c gm tool
       used by the %CImg Library to load and save compressed image formats.
       Conversely, if the \c gm executable is not auto-detected by the function,
       you can define the macro \c cimg_graphicsmagick_path with the correct path
       of the \c gm executable, before including <tt>CImg.h</tt> in your program :
       \code
       #define cimg_graphicsmagick_path "/users/thatsme/local/bin/gm"
       #include "CImg.h"

       int main() {
         CImg<> img("my_image.jpg");     // Read a JPEG image file.
         return 0;
       }
       \endcode

       Note that non compressed image formats can be read without installing ImageMagick.

       \sa temporary_path(), get_load_imagemagick_external(), load_imagemagick_external(), save_imagemagick_external().
    **/
    inline const char* graphicsmagick_path() {
      static char *st_graphicsmagick_path = 0;
      if (!st_graphicsmagick_path) {
        st_graphicsmagick_path = new char[1024];
        std::memset(st_graphicsmagick_path,0,1024);
        bool path_found = false;
        std::FILE *file = 0;
#ifdef cimg_graphicsmagick_path
        std::strcpy(st_graphicsmagick_path,cimg_graphicsmagick_path);
        if ((file=std::fopen(st_graphicsmagick_path,"r"))!=0) { std::fclose(file); path_found = true; }
#endif
#if cimg_OS==2
        const char* pf_path = programfiles_path();
        if (!path_found) {
          std::sprintf(st_graphicsmagick_path,".\\gm.exe");
          if ((file=std::fopen(st_graphicsmagick_path,"r"))!=0) { std::fclose(file); path_found = true; }
        }
        { for (int k=32; k>=10 && !path_found; --k) {
          std::sprintf(st_graphicsmagick_path,"%s\\GRAPHI~1.%.2d-\\gm.exe",pf_path,k);
          if ((file=std::fopen(st_graphicsmagick_path,"r"))!=0) { std::fclose(file); path_found = true; }
        }}
        { for (int k=9; k>=0 && !path_found; --k) {
          std::sprintf(st_graphicsmagick_path,"%s\\GRAPHI~1.%d-Q\\gm.exe",pf_path,k);
          if ((file=std::fopen(st_graphicsmagick_path,"r"))!=0) { std::fclose(file); path_found = true; }
        }}
        { for (int k=32; k>=0 && !path_found; --k) {
          std::sprintf(st_graphicsmagick_path,"%s\\GRAPHI~1.%d\\gm.exe",pf_path,k);
          if ((file=std::fopen(st_graphicsmagick_path,"r"))!=0) { std::fclose(file); path_found = true; }
        }}
        { for (int k=32; k>=10 && !path_found; --k) {
          std::sprintf(st_graphicsmagick_path,"%s\\GRAPHI~1.%.2d-\\VISUA~1\\BIN\\gm.exe",pf_path,k);
          if ((file=std::fopen(st_graphicsmagick_path,"r"))!=0) { std::fclose(file); path_found = true; }
        }}
        { for (int k=9; k>=0 && !path_found; --k) {
          std::sprintf(st_graphicsmagick_path,"%s\\GRAPHI~1.%d-Q\\VISUA~1\\BIN\\gm.exe",pf_path,k);
          if ((file=std::fopen(st_graphicsmagick_path,"r"))!=0) { std::fclose(file); path_found = true; }
        }}
        { for (int k=32; k>=0 && !path_found; --k) {
          std::sprintf(st_graphicsmagick_path,"%s\\GRAPHI~1.%d\\VISUA~1\\BIN\\gm.exe",pf_path,k);
          if ((file=std::fopen(st_graphicsmagick_path,"r"))!=0) { std::fclose(file); path_found = true; }
        }}
        { for (int k=32; k>=10 && !path_found; --k) {
          std::sprintf(st_graphicsmagick_path,"C:\\GRAPHI~1.%.2d-\\gm.exe",k);
          if ((file=std::fopen(st_graphicsmagick_path,"r"))!=0) { std::fclose(file); path_found = true; }
        }}
        { for (int k=9; k>=0 && !path_found; --k) {
          std::sprintf(st_graphicsmagick_path,"C:\\GRAPHI~1.%d-Q\\gm.exe",k);
          if ((file=std::fopen(st_graphicsmagick_path,"r"))!=0) { std::fclose(file); path_found = true; }
        }}
        { for (int k=32; k>=0 && !path_found; --k) {
          std::sprintf(st_graphicsmagick_path,"C:\\GRAPHI~1.%d\\gm.exe",k);
          if ((file=std::fopen(st_graphicsmagick_path,"r"))!=0) { std::fclose(file); path_found = true; }
        }}
        { for (int k=32; k>=10 && !path_found; --k) {
          std::sprintf(st_graphicsmagick_path,"C:\\GRAPHI~1.%.2d-\\VISUA~1\\BIN\\gm.exe",k);
          if ((file=std::fopen(st_graphicsmagick_path,"r"))!=0) { std::fclose(file); path_found = true; }
        }}
        { for (int k=9; k>=0 && !path_found; --k) {
          std::sprintf(st_graphicsmagick_path,"C:\\GRAPHI~1.%d-Q\\VISUA~1\\BIN\\gm.exe",k);
          if ((file=std::fopen(st_graphicsmagick_path,"r"))!=0) { std::fclose(file); path_found = true; }
        }}
        { for (int k=32; k>=0 && !path_found; --k) {
          std::sprintf(st_graphicsmagick_path,"C:\\GRAPHI~1.%d\\VISUA~1\\BIN\\gm.exe",k);
          if ((file=std::fopen(st_graphicsmagick_path,"r"))!=0) { std::fclose(file); path_found = true; }
        }}
        { for (int k=32; k>=10 && !path_found; --k) {
          std::sprintf(st_graphicsmagick_path,"D:\\GRAPHI~1.%.2d-\\gm.exe",k);
          if ((file=std::fopen(st_graphicsmagick_path,"r"))!=0) { std::fclose(file); path_found = true; }
        }}
        { for (int k=9; k>=0 && !path_found; --k) {
          std::sprintf(st_graphicsmagick_path,"D:\\GRAPHI~1.%d-Q\\gm.exe",k);
          if ((file=std::fopen(st_graphicsmagick_path,"r"))!=0) { std::fclose(file); path_found = true; }
        }}
        { for (int k=32; k>=0 && !path_found; --k) {
          std::sprintf(st_graphicsmagick_path,"D:\\GRAPHI~1.%d\\gm.exe",k);
          if ((file=std::fopen(st_graphicsmagick_path,"r"))!=0) { std::fclose(file); path_found = true; }
        }}
        { for (int k=32; k>=10 && !path_found; --k) {
          std::sprintf(st_graphicsmagick_path,"D:\\GRAPHI~1.%.2d-\\VISUA~1\\BIN\\gm.exe",k);
          if ((file=std::fopen(st_graphicsmagick_path,"r"))!=0) { std::fclose(file); path_found = true; }
        }}
        { for (int k=9; k>=0 && !path_found; --k) {
          std::sprintf(st_graphicsmagick_path,"D:\\GRAPHI~1.%d-Q\\VISUA~1\\BIN\\gm.exe",k);
          if ((file=std::fopen(st_graphicsmagick_path,"r"))!=0) { std::fclose(file); path_found = true; }
        }}
        { for (int k=32; k>=0 && !path_found; --k) {
          std::sprintf(st_graphicsmagick_path,"D:\\GRAPHI~1.%d\\VISUA~1\\BIN\\gm.exe",k);
          if ((file=std::fopen(st_graphicsmagick_path,"r"))!=0) { std::fclose(file); path_found = true; }
        }}
        if (!path_found) std::strcpy(st_graphicsmagick_path,"gm.exe");
#else
        if (!path_found) {
          std::sprintf(st_graphicsmagick_path,"./gm");
          if ((file=std::fopen(st_graphicsmagick_path,"r"))!=0) { std::fclose(file); path_found = true; }
        }
        if (!path_found) std::strcpy(st_graphicsmagick_path,"gm");
#endif
        winformat_string(st_graphicsmagick_path);
      }
      return st_graphicsmagick_path;
    }

    //! Return path of the \c XMedcon tool.
    /**
       If you have installed the <a href="http://xmedcon.sourceforge.net/">XMedcon package</a>
       in a standard directory, this function should return the correct path of the \c medcon tool
       used by the %CIg Library to load DICOM image formats.
       Conversely, if the \c medcon executable is not auto-detected by the function,
       you can define the macro \c cimg_medcon_path with the correct path
       of the \c medcon executable, before including <tt>CImg.h</tt> in your program :
       \code
       #define cimg_medcon_path "/users/thatsme/local/bin/medcon"
       #include "CImg.h"

       int main() {
         CImg<> img("my_image.dcm");    // Read a DICOM image file.
         return 0;
       }
       \endcode

       Note that \c medcon is only needed if you want to read DICOM image formats.

       \sa temporary_path(), get_load_medcon_external(), load_medcon_external().
    **/
    inline const char* medcon_path() {
      static char *st_medcon_path = 0;
      if (!st_medcon_path) {
        st_medcon_path = new char[1024];
        std::memset(st_medcon_path,0,1024);
        bool path_found = false;
        std::FILE *file = 0;
#ifdef cimg_medcon_path
        std::strcpy(st_medcon_path,cimg_medcon_path);
        if ((file=std::fopen(st_medcon_path,"r"))!=0) { std::fclose(file); path_found = true; }
#endif
#if cimg_OS==2
        const char* pf_path = programfiles_path();
        if (!path_found) {
          std::sprintf(st_medcon_path,".\\medcon.bat");
          if ((file=std::fopen(st_medcon_path,"r"))!=0) { std::fclose(file); path_found = true; }
        }
        if (!path_found) {
          std::sprintf(st_medcon_path,".\\medcon.exe");
          if ((file=std::fopen(st_medcon_path,"r"))!=0) { std::fclose(file); path_found = true; }
        }
        if (!path_found) {
          std::sprintf(st_medcon_path,"%s\\XMedCon\\bin\\medcon.bat",pf_path);
          if ((file=std::fopen(st_medcon_path,"r"))!=0) { std::fclose(file); path_found = true; }
        }
        if (!path_found) {
          std::sprintf(st_medcon_path,"%s\\XMedCon\\bin\\medcon.exe",pf_path);
          if ((file=std::fopen(st_medcon_path,"r"))!=0) { std::fclose(file); path_found = true; }
        }
        if (!path_found) std::strcpy(st_medcon_path,"medcon.bat");
#else
        if (!path_found) {
          std::sprintf(st_medcon_path,"./medcon");
          if ((file=std::fopen(st_medcon_path,"r"))!=0) { std::fclose(file); path_found = true; }
        }
        if (!path_found) std::strcpy(st_medcon_path,"medcon");
#endif
        winformat_string(st_medcon_path);
      }
      return st_medcon_path;
    }

    //! Return path to store temporary files.
    /**
       If you are running on a standard Unix or Windows system, this function should return a correct path
       where temporary files can be stored. If such a path is not auto-detected by this function,
       you can define the macro \c cimg_temporary_path with a correct path, before including <tt>CImg.h</tt>
       in your program :
       \code
       #define cimg_temporary_path "/users/thatsme/tmp"
       #include "CImg.h"

       int main() {
         CImg<> img("my_image.jpg");   // Read a JPEG image file (using the defined temporay path).
         return 0;
       }
       \endcode

       A temporary path is necessary to load and save compressed image formats, using \c convert
       or \c medcon.

       \sa imagemagick_path(), get_load_imagemagick_external(), load_imagemagick_external(), save_imagemagick_external(), get_load_medcon_external(), load_medcon_external().
    **/
    inline const char* temporary_path() {

#define cimg_test_temporary_path(p) \
      if (!path_found) { \
        std::sprintf(st_temporary_path,"%s",p);				\
        std::sprintf(tmp,"%s%s%s",st_temporary_path,cimg_OS==2?"\\":"/",filetmp); \
        if ((file=std::fopen(tmp,"wb"))!=0) { std::fclose(file); std::remove(tmp); path_found = true; } \
      }

      static char *st_temporary_path = 0;
      if (!st_temporary_path) {
        st_temporary_path = new char[1024];
        std::memset(st_temporary_path,0,1024);
        bool path_found = false;
        char tmp[1024], filetmp[512];
        std::FILE *file = 0;
        std::sprintf(filetmp,"%s.tmp",cimg::filenamerand());
#ifdef cimg_temporary_path
        cimg_test_temporary_path(cimg_temporary_path);
#endif
        char *tmpPath = getenv("TMP");
        if (tmpPath==NULL) { tmpPath = getenv("TEMP"); winformat_string(tmpPath); }
        if (tmpPath!=NULL) 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_temporary_path[0]='\0';
          std::strcpy(tmp,filetmp);
          if ((file=std::fopen(tmp,"wb"))!=0) { std::fclose(file); std::remove(tmp); path_found = true; }
        }
        if (!path_found)
          throw CImgIOException("cimg::temporary_path() : Unable to find a temporary path accessible for writing\n"
                                "you have to set the macro 'cimg_temporary_path' to a valid path where you have writing access :\n"
                                "#define cimg_temporary_path \"path\" (before including 'CImg.h')");
      }
      return st_temporary_path;
    }

    inline const char *filename_split(const char *const filename, char *const body=0) {
      if (!filename) { if (body) body[0]='\0'; return 0; }
      int l = cimg::strfind(filename,'.');
      if (l>=0) { if (body) { std::strncpy(body,filename,l); body[l]='\0'; }}
      else { if (body) std::strcpy(body,filename); l=(int)std::strlen(filename)-1; }
      return filename+l+1;
    }

    inline char* filename_number(const char *const filename, const int number, const unsigned int n, char *const string) {
      if (!filename) { if (string) string[0]='\0'; return 0; }
      char format[1024],body[1024];
      const char *ext = cimg::filename_split(filename,body);
      if (n>0) std::sprintf(format,"%s_%%.%ud.%s",body,n,ext);
      else std::sprintf(format,"%s_%%d.%s",body,ext);
      std::sprintf(string,format,number);
      return string;
    }

    inline std::FILE *fopen(const char *const path, const char *const mode) {
      if(!path || !mode)
        throw CImgArgumentException("cimg::fopen() : File '%s' cannot be opened with mode '%s'.",
                                    path?path:"(null)",mode?mode:"(null)");
      if (path[0]=='-') return (mode[0]=='r')?stdin:stdout;
      std::FILE *dest = std::fopen(path,mode);
      if (!dest)
        throw CImgIOException("cimg::fopen() : File '%s' cannot be opened%s",
                              path,mode[0]=='r'?" for reading.":(mode[0]=='w'?" for writing.":"."),path);
      return dest;
    }

    inline int fclose(std::FILE *file) {
      if (!file) warn("cimg::fclose() : Can't close (null) file");
      if (!file || file==stdin || file==stdout) return 0;
      const int errn = std::fclose(file);
      if (errn!=0) warn("cimg::fclose() : Error %d during file closing",errn);
      return errn;
    }

    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() : Can't read %u x %u bytes of file pointer '%p' in buffer '%p'",
                                    nmemb,sizeof(T),stream,ptr);
      const unsigned long wlimitT = 63*1024*1024, wlimit = wlimitT/sizeof(T);
      unsigned int toread = nmemb, alread = 0, ltoread = 0, lalread = 0;
      do {
        ltoread = (toread*sizeof(T))<wlimitT?toread:wlimit;
        lalread = (unsigned int)std::fread((void*)(ptr+alread),sizeof(T),ltoread,stream);
        alread+=lalread;
        toread-=lalread;
      } while (ltoread==lalread && toread>0);
      if (toread>0) warn("cimg::fread() : File reading problems, only %u/%u elements read",alread,nmemb);
      return alread;
    }

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

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

    template<typename T1, typename T2> inline void swap(T1& a1, T1& b1, T2& a2, T2& b2) {
      cimg::swap(a1,b1); cimg::swap(a2,b2);
    }

    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);
    }

    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);
    }

    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);
    }

    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);
    }

    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);
    }

    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);
    }

    template<typename T> inline void endian_swap(T* const buffer, const unsigned int 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) cimg::swap(*(pb++),*(--pe));
        } break;
      }
      }
    }

    template<typename T> inline T& endian_swap(T& a) {
      endian_swap(&a,1);
      return a;
    }

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

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

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

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

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

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

    inline const char* argument(const unsigned int nb, const int argc, char **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;
    }

    template<typename T> inline const T rol(const T a, const unsigned int n=1) {
      return n?(T)((a<<n)|(a>>((sizeof(T)<<3)-n))):a;
    }

    template<typename T> inline const T ror(const T a, const unsigned int n=1) {
      return n?(T)((a>>n)|(a<<((sizeof(T)<<3)-n))):a;
    }

    //! Return the absolute value of \p a
    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);
    }

    template<typename T> inline T sqr(const T val) {
      return val*val;
    }

    //! Return the minimum between \p a and \p b.
    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 restype;
      return (restype)(a<=b?a:b);
    }

    //! Return the minimum between \p a,\p b and \a c.
    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 restype;
      return (restype)cimg::min(cimg::min(a,b),c);
    }

    //! Return the minimum between \p a,\p b,\p c and \p d.
    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 restype;
      return (restype)cimg::min(cimg::min(a,b,c),d);
    }

    //! Return the maximum between \p a and \p b.
    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 restype;
      return (restype)(a>=b?a:b);
    }

    //! Return the maximum between \p a,\p b and \p c.
    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 restype;
      return (restype)cimg::max(cimg::max(a,b),c);
    }

    //! Return the maximum between \p a,\p b,\p c and \p d.
    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 restype;
      return (restype)cimg::max(cimg::max(a,b,c),d);
    }

    //! Return the sign of \p x.
    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 \p x.
    template<typename T> inline unsigned long nearest_pow2(const T x) {
      unsigned long i=1;
      while (x>i) i<<=1;
      return i;
    }

    //! Return \p x modulo \p m (generic modulo).
    /**
       This modulo function accepts negative and floating-points modulo numbers \p m.
    **/
    template<typename T> inline T mod(const T& x, const T& m) {
      const double dx = (double)x, dm = (double)m;
      if (x<0) { return (T)(dm+dx+dm*std::floor(-dx/dm)); }
      return (T)(dx-dm*std::floor(dx/dm));
    }

    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 minmod(\p a,\p b).
    /**
       The operator minmod(\p a,\p b) is defined to be :
       - minmod(\p a,\p b) = min(\p a,\p b), if (\p a * \p b)>0.
       - minmod(\p a,\p b) = 0,              if (\p a * \p b)<=0
    **/
    template<typename T> inline T minmod(const T a, const T b) {
      return a*b<=0?0:(a>0?(a<b?a:b):(a<b?b:a));
    }

    //! Return a random variable between [0,1], followin a uniform distribution.
    inline double rand() {
      return (double)std::rand()/RAND_MAX;
    }

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

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

    //! Return a rounded number
    inline double round(const double x, const double y, const unsigned int round_type=0) {
      if (y<=0) return x;
      const double delta = cimg::mod(x,y);
      if (delta==0.0) return x;
      const double
        backward = x - delta,
        forward = backward + y;
      return round_type==1?backward:(round_type==2?forward:(2*delta<y?backward:forward));
    }

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

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

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

      std::fprintf(stderr,"  > 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_purple,
                   cimg_OS,
                   cimg::t_normal);

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

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

      std::fprintf(stderr,"  > Color terminal :         %s%-13s%s %s('cimg_color_terminal' %s)%s\n",
                   cimg::t_bold,
#ifdef cimg_color_terminal
                   "Yes",cimg::t_normal,cimg::t_purple,"defined",
#else
                   "No",cimg::t_normal,cimg::t_purple,"undefined",
#endif
                   cimg::t_normal);

      std::fprintf(stderr,"  > Debug messages :         %s%-13s%s %s('cimg_debug'=%d)%s\n",
                   cimg::t_bold,
                   cimg_debug==0?"No":(cimg_debug==1 || cimg_debug==2?"Yes":(cimg_debug==3?"Yes+":"Unknown")),
                   cimg::t_normal,cimg::t_purple,
                   cimg_debug,
                   cimg::t_normal);

#if cimg_display_type==1
      std::fprintf(stderr,"  > 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_purple,"defined",
#else
                   "No",cimg::t_normal,cimg::t_purple,"undefined",
#endif
                   cimg::t_normal);

      std::fprintf(stderr,"  > 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_purple,"defined",
#else
                   "No",cimg::t_normal,cimg::t_purple,"undefined",
#endif
                   cimg::t_normal);
#endif
      std::fprintf(stderr,"  > 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_purple,"defined",
#else
                   "No",cimg::t_normal,cimg::t_purple,"undefined",
#endif
                   cimg::t_normal);
      std::fprintf(stderr,"  > 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_purple,"defined",
#else
                   "No",cimg::t_normal,cimg::t_purple,"undefined",
#endif
                   cimg::t_normal);
      std::fprintf(stderr,"  > 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_purple,"defined",
#else
                   "No",cimg::t_normal,cimg::t_purple,"undefined",
#endif
                   cimg::t_normal);

      std::fprintf(stderr,"  > 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_purple,"defined",
#else
                   "No",cimg::t_normal,cimg::t_purple,"undefined",
#endif
                   cimg::t_normal);

      std::fprintf(stderr,"  > 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_purple,"defined",
#else
                   "No",cimg::t_normal,cimg::t_purple,"undefined",
#endif
                   cimg::t_normal);

      std::fprintf(stderr,"  > 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_purple,"defined",
#else
                   "No",cimg::t_normal,cimg::t_purple,"undefined",
#endif
                   cimg::t_normal);

      std::fprintf(stderr,"  > 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_purple,"defined",
#else
                   "No",cimg::t_normal,cimg::t_purple,"undefined",
#endif
                   cimg::t_normal);

      std::sprintf(tmp,"\"%.1020s\"",cimg::imagemagick_path());
      std::fprintf(stderr,"  > Path of ImageMagick :    %s%-13s%s %s('cimg_imagemagick_path'%s)%s\n",
                   cimg::t_bold,
                   tmp,
                   cimg::t_normal,
#ifdef cimg_imagemagick_path
                   cimg::t_purple,"=\""cimg_imagemagick_path"\"",
#else
                   cimg::t_purple," undefined",
#endif
                   cimg::t_normal);

      std::sprintf(tmp,"\"%.1020s\"",cimg::graphicsmagick_path());
      std::fprintf(stderr,"  > Path of GraphicsMagick : %s%-13s%s %s('cimg_graphicsmagick_path'%s)%s\n",
                   cimg::t_bold,
                   tmp,
                   cimg::t_normal,
#ifdef cimg_graphicsmagick_path
                   cimg::t_purple,"=\""cimg_graphicsmagick_path"\"",
#else
                   cimg::t_purple," undefined",
#endif
                   cimg::t_normal);

      std::sprintf(tmp,"\"%.1020s\"",cimg::medcon_path());
      std::fprintf(stderr,"  > Path of 'medcon' :       %s%-13s%s %s('cimg_medcon_path'%s)%s\n",
                   cimg::t_bold,
                   tmp,
                   cimg::t_normal,
#ifdef cimg_medcon_path
                   cimg::t_purple,"=\""cimg_medcon_path"\"",
#else
                   cimg::t_purple," undefined",
#endif
                   cimg::t_normal);

      std::sprintf(tmp,"\"%.1020s\"",cimg::temporary_path());
      std::fprintf(stderr,"  > Temporary path :         %s%-13s%s %s('cimg_temporary_path'%s)%s\n",
                   cimg::t_bold,
                   tmp,
                   cimg::t_normal,
#ifdef cimg_temporary_path
                   cimg::t_purple,"=\""cimg_temporary_path"\"",
#else
                   cimg::t_purple," undefined",
#endif
                   cimg::t_normal);

      std::fprintf(stderr,"\n");
    }

#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.
   #
   #
   -------------------------------------------------*/

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  template<typename T, typename t> inline CImg<T> apply(const CImg<T>& instance, t& func) {
    return instance.get_apply(func);
  }

  template<typename T, typename t> inline CImg<typename cimg::superset<T,t>::type> mul(const CImg<T>& instance, const CImg<t>& img) {
    return instance.get_mul(img);
  }

  template<typename T, typename t> inline CImg<typename cimg::superset<T,t>::type> div(const CImg<T>& instance, const CImg<t>& img) {
    return instance.get_div(img);
  }

  template<typename T, typename t> inline CImg<typename cimg::superset<T,t>::type> max(const CImg<T>& instance, const CImg<t>& img) {
    return instance.get_max(img);
  }

  template<typename T> inline CImg<T> max(const CImg<T>& instance, const T val) {
    return instance.get_max(val);
  }

  template<typename T, typename t> inline CImg<typename cimg::superset<T,t>::type> min(const CImg<T>& instance, const CImg<t>& img) {
    return instance.get_min(img);
  }

  template<typename T> inline CImg<typename cimg::last<T,double>::type> stats(const CImg<T>& instance) {
    return instance.get_stats();
  }

  template<typename T> inline CImg<T> min(const CImg<T>& instance, const T val) {
    return instance.get_min(val);
  }

  template<typename T> inline CImg<typename cimg::superset<T,float>::type> sqr(const CImg<T>& instance) {
    return instance.get_sqr();
  }

  template<typename T> inline CImg<typename cimg::superset<T,float>::type> sqrt(const CImg<T>& instance) {
    return instance.get_sqrt();
  }

  template<typename T> inline CImg<typename cimg::superset<T,float>::type> exp(const CImg<T>& instance) {
    return instance.get_exp();
  }

  template<typename T> inline CImg<typename cimg::superset<T,float>::type> log(const CImg<T>& instance) {
    return instance.get_log();
  }

  template<typename T> inline CImg<typename cimg::superset<T,float>::type> log10(const CImg<T>& instance) {
    return instance.get_log10();
  }

  template<typename T> inline CImg<typename cimg::superset<T,float>::type> pow(const CImg<T>& instance, const double p) {
    return instance.get_pow(p);
  }

  template<typename T, typename t> inline CImg<typename cimg::superset<T,float>::type> pow(const CImg<T>& instance, const CImg<t>& img) {
    return instance.get_pow(img);
  }

  template<typename T> inline CImg<typename cimg::superset<T,float>::type> abs(const CImg<T>& instance) {
    return instance.get_abs();
  }

  template<typename T> inline CImg<typename cimg::superset<T,float>::type> cos(const CImg<T>& instance) {
    return instance.get_cos();
  }

  template<typename T> inline CImg<typename cimg::superset<T,float>::type> sin(const CImg<T>& instance) {
    return instance.get_sin();
  }

  template<typename T> inline CImg<typename cimg::superset<T,float>::type> tan(const CImg<T>& instance) {
    return instance.get_tan();
  }

  template<typename T> inline CImg<typename cimg::superset<T,float>::type> acos(const CImg<T>& instance) {
    return instance.get_acos();
  }

  template<typename T> inline CImg<typename cimg::superset<T,float>::type> asin(const CImg<T>& instance) {
    return instance.get_asin();
  }

  template<typename T> inline CImg<typename cimg::superset<T,float>::type> atan(const CImg<T>& instance) {
    return instance.get_atan();
  }

  template<typename T> inline CImg<T> round(const CImg<T>& instance, const float x, const unsigned int round_type=0) {
    return instance.get_round(x,round_type);
  }

  template<typename T> inline CImg<T> rand(const CImg<T>& instance, const T val_min, const T val_max) {
    return instance.get_rand(val_min,val_max);
  }

  template<typename T> inline CImg<T> fill(const CImg<T>& instance, const T val) {
    return instance.get_fill(val);
  }

  template<typename T> inline CImg<T> fill(const CImg<T>& instance, const T val0, const T val1) {
    return instance.get_fill(val0,val1);
  }

  template<typename T> inline CImg<T> fill(const CImg<T>& instance, const T val0, const T val1, const T val2) {
    return instance.get_fill(val0,val1,val2);
  }

  template<typename T> inline CImg<T> fill(const CImg<T>& instance, const T val0, const T val1, const T val2, const T val3) {
    return instance.get_fill(val0,val1,val2,val3);
  }

  template<typename T> inline CImg<T> fill(const CImg<T>& instance, const T val0, const T val1, const T val2, const T val3,
					   const T val4) {
    return instance.get_fill(val0,val1,val2,val3,val4);
  }

  template<typename T> inline CImg<T> fill(const CImg<T>& instance, const T val0, const T val1, const T val2, const T val3,
					   const T val4, const T val5) {
    return instance.get_fill(val0,val1,val2,val3,val4,val5);
  }

  template<typename T> inline CImg<T> fill(const CImg<T>& instance, const T val0, const T val1, const T val2, const T val3,
					   const T val4, const T val5, const T val6) {
    return instance.get_fill(val0,val1,val2,val3,val4,val5,val6);
  }

  template<typename T> inline CImg<T> fill(const CImg<T>& instance, const T val0, const T val1, const T val2, const T val3,
					   const T val4, const T val5, const T val6, const T val7) {
    return instance.get_fill(val0,val1,val2,val3,val4,val5,val6,val7);
  }

  template<typename T> inline CImg<T> fill(const CImg<T>& instance, 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) {
    return instance.get_fill(val0,val1,val2,val3,val4,val5,val6,val7,val8);
  }

  template<typename T> inline CImg<T> fill(const CImg<T>& instance, 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) {
    return instance.get_fill(val0,val1,val2,val3,val4,val5,val6,val7,val8,val9);
  }

  template<typename T> inline CImg<T> fill(const CImg<T>& instance, 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) {
    return instance.get_fill(val0,val1,val2,val3,val4,val5,val6,val7,val8,val9,val10);
  }

  template<typename T> inline CImg<T> fill(const CImg<T>& instance, 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) {
    return instance.get_fill(val0,val1,val2,val3,val4,val5,val6,val7,val8,val9,val10,val11);
  }

  template<typename T> inline CImg<T> fill(const CImg<T>& instance, 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) {
    return instance.get_fill(val0,val1,val2,val3,val4,val5,val6,val7,val8,val9,val10,val11,val12);
  }

  template<typename T> inline CImg<T> fill(const CImg<T>& instance, 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) {
    return instance.get_fill(val0,val1,val2,val3,val4,val5,val6,val7,val8,val9,val10,val11,val12,val13);
  }

  template<typename T> inline CImg<T> fill(const CImg<T>& instance, 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) {
    return instance.get_fill(val0,val1,val2,val3,val4,val5,val6,val7,val8,val9,val10,val11,val12,val13,val14);
  }

  template<typename T> inline CImg<T> fill(const CImg<T>& instance, 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) {
    return instance.get_fill(val0,val1,val2,val3,val4,val5,val6,val7,val8,val9,val10,val11,val12,val13,val14,val15);
  }

  template<typename T, int N> inline CImg<T> fill(const CImg<T>& instance, const int val0, ...) {
    CImg<T> res(instance,false);
    va_list ap;
    va_start(ap,val0);
    res.template _fill<N,int>(val0,ap);
    va_end(ap);
    return res;
  }

  template<typename T, int N> inline CImg<T> fill(const CImg<T>& instance, const double val0, ...) {
    CImg<T> res(instance,false);
    va_list ap;
    va_start(ap,val0);
    res.template _fill<N,double>(val0,ap);
    va_end(ap);
    return res;
  }

  template<typename T> inline CImg<T> normalize(const CImg<T>& instance, const T a, const T b) {
    return instance.get_normalize(a,b);
  }

  template<typename T> inline CImg<T> cut(const CImg<T>& instance, const T a, const T b) {
    return instance.get_cut(a,b);
  }

  template<typename T> inline CImg<T> quantize(const CImg<T>& instance, const unsigned int n=256, const bool keep_range=true) {
    return instance.get_quantize(n,keep_range);
  }

  template<typename T> inline CImg<T> threshold(const CImg<T>& instance, const T thres) {
    return instance.get_threshold(thres);
  }

  template<typename T> inline CImg<T> rotate(const CImg<T>& instance, const float angle, const unsigned int cond=3) {
    return instance.get_rotate(angle,cond);
  }

  template<typename T> inline CImg<T> rotate(const CImg<T>& instance, const float angle, const float cx, const float cy,
					     const float zoom=1, const unsigned int cond=3) {
    return instance.get_rotate(angle,cx,cy,zoom,cond);
  }

  template<typename T> inline CImg<T> resize(const CImg<T>& instance,
					     const int pdx=-100, const int pdy=-100, const int pdz=-100, const int pdv=-100,
					     const int interp=1, const int border_condition=-1, const bool center=false) {
    return instance.get_resize(pdx,pdy,pdz,pdv,interp,border_condition,center);
  }

  template<typename T, typename t> inline CImg<T> resize(const CImg<T>& instance, const CImg<t>& src,
                                                         const int interp=1, const int border_condition=-1,
                                                         const bool center=false) {
    return instance.get_resize(src,interp,border_condition,center);
  }

  template<typename T> inline CImg<T> resize(const CImg<T>& instance, const CImgDisplay& disp,
					     const int interp=1, const int border_condition=-1, const bool center=false) {
    return instance.get_resize(disp,interp,border_condition,center);
  }

  template<typename T, typename t> inline CImg<t> permute_axes(const CImg<T>& instance, const char *permut, const t& pixel_type) {
    return instance.get_permute_axes(instance,permut,pixel_type);
  }

  template<typename T> inline CImg<T> permute_axes(const CImg<T>& instance, const char *permut="vxyz") {
    return instance.get_permute_axes(instance,permut);
  }

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

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

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

  template<typename T> inline CImg<T> crop(const CImg<T>& instance, const int x0, const int y0, const int z0, const int v0,
					   const int x1, const int y1, const int z1, const int v1,
					   const bool border_condition=false) {
    return instance.get_crop(x0,y0,z0,v0,x1,y1,z1,v1,border_condition);
  }

  template<typename T> inline CImg<T> crop(const CImg<T>& instance, const int x0, const int y0, const int z0,
					   const int x1, const int y1, const int z1,
					   const bool border_condition=false) {
    return instance.get_crop(x0,y0,z0,x1,y1,z1,border_condition);
  }

  template<typename T> inline CImg<T> crop(const CImg<T>& instance, const int x0, const int y0,
					   const int x1, const int y1,
					   const bool border_condition=false) {
    return instance.get_crop(x0,y0,x1,y1,border_condition);
  }

  template<typename T> inline CImg<T> crop(const CImg<T>& instance, const int x0, const int x1,
					   const bool border_condition=false) {
    return instance.get_crop(x0,x1,border_condition);
  }

  template<typename T> inline CImg<T> columns(const CImg<T>& instance, const unsigned int x0, const unsigned int x1) {
    return instance.get_columns(x0,x1);
  }

  template<typename T> inline CImg<T> column(const CImg<T>& instance, const unsigned int x0) {
    return instance.get_column(x0);
  }

  template<typename T> inline CImg<T> lines(const CImg<T>& instance, const unsigned int y0, const unsigned int y1) {
    return instance.get_lines(y0,y1);
  }

  template<typename T> inline CImg<T> line(const CImg<T>& instance, const unsigned int y0) {
    return instance.get_line(y0);
  }

  template<typename T> inline CImg<T> slices(const CImg<T>& instance, const unsigned int z0, const unsigned int z1) {
    return instance.get_slices(z0,z1);
  }

  template<typename T> inline CImg<T> slice(const CImg<T>& instance, const unsigned int z0) {
    return instance.get_slice(z0);
  }

  template<typename T> inline CImg<T> channels(const CImg<T>& instance, const unsigned int v0, const unsigned int v1) {
    return instance.get_channels(v0,v1);
  }

  template<typename T> inline CImg<T> channel(const CImg<T>& instance, const unsigned int v0) {
    return instance.get_channel(v0);
  }

  template<typename T> inline CImg<T> shared_points(CImg<T>& instance, const unsigned int x0, const unsigned int x1,
						    const unsigned int y0=0, const unsigned int z0=0, const unsigned int v0=0) {
    return instance.get_shared_points(x0,x1,y0,z0,v0);
  }

  template<typename T> inline CImg<T> shared_points(const CImg<T>& instance, const unsigned int x0, const unsigned int x1,
						    const unsigned int y0=0, const unsigned int z0=0, const unsigned int v0=0) {
    return instance.get_shared_points(x0,x1,y0,z0,v0);
  }

  template<typename T> inline CImg<T> shared_lines(CImg<T>& instance, const unsigned int y0, const unsigned int y1,
						   const unsigned int z0=0, const unsigned int v0=0) {
    return instance.get_shared_lines(y0,y1,z0,v0);
  }

  template<typename T> inline CImg<T> shared_lines(const CImg<T>& instance, const unsigned int y0, const unsigned int y1,
						   const unsigned int z0=0, const unsigned int v0=0) {
    return instance.get_shared_lines(y0,y1,z0,v0);
  }

  template<typename T> inline CImg<T> shared_line(CImg<T>& instance,
						  const unsigned int y0, const unsigned int z0=0, const unsigned int v0=0) {
    return instance.get_shared_line(y0,z0,v0);
  }

  template<typename T> inline CImg<T> shared_line(const CImg<T>& instance,
						  const unsigned int y0, const unsigned int z0=0, const unsigned int v0=0) {
    return instance.get_shared_line(y0,z0,v0);
  }

  template<typename T> inline CImg<T> shared_planes(CImg<T>& instance,
						    const unsigned int z0, const unsigned int z1, const unsigned int v0=0) {
    return instance.get_shared_planes(z0,z1,v0);
  }

  template<typename T> inline CImg<T> shared_planes(const CImg<T>& instance,
						    const unsigned int z0, const unsigned int z1, const unsigned int v0=0) {
    return instance.get_shared_planes(z0,z1,v0);
  }

  template<typename T> inline CImg<T> shared_plane(CImg<T>& instance, const unsigned int z0, const unsigned int v0=0) {
    return instance.get_shared_plane(z0,v0);
  }

  template<typename T> inline CImg<T> shared_plane(const CImg<T>& instance, const unsigned int z0, const unsigned int v0=0) {
    return instance.get_shared_plane(z0,v0);
  }

  template<typename T> inline CImg<T> shared_channels(CImg<T>& instance, const unsigned int v0, const unsigned int v1) {
    return instance.get_shared_channels(v0,v1);
  }

  template<typename T> inline CImg<T> shared_channels(const CImg<T>& instance, const unsigned int v0, const unsigned int v1) {
    return instance.get_shared_channels(v0,v1);
  }

  template<typename T> inline CImg<T> shared_channel(CImg<T>& instance, const unsigned int v0) {
    return instance.get_shared_channel(v0);
  }

  template<typename T> inline CImg<T> shared_channel(const CImg<T>& instance, const unsigned int v0) {
    return instance.get_shared_channel(v0);
  }

  template<typename T> inline CImg<T> shared(CImg<T>& instance) {
    return instance.get_shared();
  }

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

  template<typename T> inline CImg<T> mirror(const CImg<T>& instance, const char axe='x') {
    return instance.get_mirror(axe);
  }

  template<typename T> inline CImg<T> translate(const CImg<T>& instance, const int deltax, const int deltay=0, const int deltaz=0,
						const int deltav=0, const int border_condition=0) {
    return instance.get_translate(deltax,deltay,deltaz,deltav,border_condition);
  }

  template<typename T> inline CImg<T> projections2d(const CImg<T>& instance,
						    const unsigned int x0, const unsigned int y0, const unsigned int z0,
                                                    const int dx=-100, const int dy=-100, const int dz=-100) {
    return instance.get_projections2d(x0,y0,z0,dx,dy,dz);
  }

  template<typename T> inline CImg<typename cimg::last<T,float>::type>
  histogram(const CImg<T>& instance, const unsigned int nblevels=256, const T val_min=(T)0, const T val_max=(T)0) {
    return instance.get_histogram(nblevels,val_min,val_max);
  }

  template<typename T> inline CImg<T> equalize_histogram(const CImg<T>& instance,
							 const unsigned int nblevels=256, const T val_min=(T)0, const T val_max=(T)0) {
    return instance.get_equalize_histogram(nblevels,val_min,val_max);
  }

  template<typename T> inline CImg<typename cimg::last<T,unsigned int>::type> label_regions(const CImg<T>& instance) {
    return instance.get_label_regions();
  }

  template<typename T> inline CImg<typename cimg::superset<T,float>::type> norm_pointwise(const CImg<T>& instance, int norm_type=2) {
    return instance.get_norm_pointwise(norm_type);
  }

  template<typename T> inline CImg<typename cimg::superset<T,float>::type> orientation_pointwise(const CImg<T>& instance) {
    return instance.get_orientation_pointwise();
  }

  template<typename T> inline CImgList<T> split(const CImg<T>& instance, const char axe='x', const unsigned int nb=0) {
    return instance.get_split(axe,nb);
  }

  template<typename T> inline CImg<T> append(const CImg<T>& instance, const CImg<T>& img, const char axis='x', const char align='c') {
    return instance.get_append(img,axis,align);
  }

  template<typename T> inline CImgList<typename cimg::superset<T,float>::type> gradientXY(const CImg<T>& instance, const int scheme=0) {
    return instance.get_gradientXY(scheme);
  }

  template<typename T> inline CImgList<typename cimg::superset<T,float>::type> gradientXYZ(const CImg<T>& instance, const int scheme=0) {
    return instance.get_gradientXYZ(scheme);
  }

  template<typename T> inline CImg<typename cimg::superset<T,float>::type> structure_tensorXY(const CImg<T>& instance, const int scheme=1) {
    return instance.get_structure_tensorXY(scheme);
  }

  template<typename T> inline CImg<typename cimg::superset<T,float>::type> structure_tensorXYZ(const CImg<T>& instance, const int scheme=1) {
    return instance.get_structure_tensorXYZ(scheme);
  }

  template<typename T> inline CImg<typename cimg::superset<T,float>::type>
  distance_function(const CImg<T>& instance, const unsigned int nb_iter=100, const float band_size=0.0f, const float precision=0.5f) {
    return instance.get_distance_function(nb_iter,band_size,precision);
  }

  template<typename T, typename t> inline CImg<typename cimg::superset<T,float>::type>
  dijkstra(const CImg<T>& instance, const unsigned int starting_node, const unsigned int ending_node, CImg<t>& previous) {
    return instance.get_dijkstra(starting_node,ending_node,previous);
  }

  template<typename T, typename t> inline CImg<typename cimg::superset<T,float>::type>
  dijkstra(const CImg<T>& instance, const unsigned int starting_node, const unsigned int ending_node=~0U) {
    return instance.get_dijkstra(starting_node,ending_node);
  }

  template<typename T, typename t> inline CImg<t> RGBtoLUT(const CImg<T>& instance, const CImg<t>& palette,
							  const bool dithering=true, const bool indexing=false) {
    return instance.get_RGBtoLUT(palette,dithering,indexing);
  }

  template<typename T> inline CImg<T> RGBtoLUT(const CImg<T>& instance, const bool dithering=true, const bool indexing=false) {
    return instance.get_RGBtoLUT(dithering,indexing);
  }

  template<typename T, typename t> inline CImg<t> LUTtoRGB(const CImg<T>& instance, const CImg<t>& palette) {
    return instance.get_LUTtoRGB(palette);
  }

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

  template<typename T> inline CImg<typename cimg::superset<T,float>::type> RGBtoHSV(const CImg<T>& instance) {
    return instance.get_RGBtoHSV();
  }

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

  template<typename T> inline CImg<typename cimg::superset<T,float>::type> RGBtoHSL(const CImg<T>& instance) {
    return instance.get_RGBtoHSL();
  }

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

  template<typename T> inline CImg<typename cimg::superset<T,float>::type> RGBtoHSI(const CImg<T>& instance) {
    return instance.get_RGBtoHSI();
  }

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

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

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

  template<typename T> inline CImg<typename cimg::superset<T,float>::type> RGBtoYUV(const CImg<T>& instance) {
    return instance.get_RGBtoYUV();
  }

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

  template<typename T> inline CImg<typename cimg::superset<T,float>::type> RGBtoXYZ(const CImg<T>& instance) {
    return instance.get_RGBtoXYZ();
  }

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

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

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

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

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

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

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

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

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

  template<typename T> inline CImg<T> RGBtoBayer(const CImg<T>& instance, const bool even_mode=true) {
    return instance.get_RGBtoBayer(even_mode);
  }

  template<typename T> inline CImg<T> BayertoRGB(const CImg<T>& instance, const unsigned int interpolation_type=3, const bool even_mode=true) {
    return instance.get_BayertoRGB(interpolation_type,even_mode);
  }

  template<typename T, typename t> inline CImg<typename cimg::superset<T,t>::type>
  correlate(const CImg<T>& instance, const CImg<t>& mask, const unsigned int cond=1, const bool weighted_correl=false) {
    return instance.get_correlate(mask,cond,weighted_correl);
  }

  template<typename T, typename t> inline CImg<typename cimg::superset<T,t>::type>
  convolve(const CImg<T>& instance, const CImg<t>& mask, const unsigned int cond=1, const bool weighted_convol=false) {
    return instance.get_convolve(mask,cond,weighted_convol);
  }

  template<typename T, typename t> inline CImg<typename cimg::superset<T,t>::type>
  erode(const CImg<T>& instance, const CImg<t>& mask, const unsigned int cond=1, const bool weighted_erosion=false) {
    return instance.get_erode(mask,cond,weighted_erosion);
  }

  template<typename T> inline CImg<T> erode(const CImg<T>& instance, const unsigned int n, const unsigned int cond=1) {
    return instance.get_erode(n,cond);
  }

  template<typename T, typename t> inline CImg<typename cimg::superset<T,t>::type>
  dilate(const CImg<T>& instance, const CImg<t>& mask, const unsigned int cond=1, const bool weighted_dilatation=false) {
    return instance.get_dilate(mask,cond,weighted_dilatation);
  }

  template<typename T> inline CImg<T> dilate(const CImg<T>& instance, const unsigned int n, const unsigned int cond=1) {
    return instance.get_dilate(n,cond);
  }

  template<typename T> inline CImg<T> noise(const CImg<T>& instance, const double sigma=-20, const unsigned int ntype=0) {
    return instance.get_noise(sigma,ntype);
  }

  template<typename T> inline CImg<typename cimg::superset<T,float>::type>
  deriche(const CImg<T>& instance, const float sigma, const int order=0, const char axe='x', const bool cond=true) {
    return instance.get_deriche(sigma,order,axe,cond);
  }

  template<typename T> inline CImg<typename cimg::superset<T,float>::type>
  blur(const CImg<T>& instance, const float sigmax, const float sigmay, const float sigmaz, const bool cond=true) {
    return instance.get_blur(sigmax,sigmay,sigmaz,cond);
  }

  template<typename T> inline CImg<typename cimg::superset<T,float>::type>
  blur(const CImg<T>& instance, const float sigma, const bool cond=true) {
    return instance.get_blur(sigma,cond);
  }

  template<typename T, typename t> inline CImg<T> blur_anisotropic(const CImg<T>& instance, const CImg<t>& G, const float amplitude=60.0f,
                                                                   const float dl=0.8f, const float da=30.0f,
                                                                   const float gauss_prec=2.0f, const unsigned int interpolation=0,
                                                                   const bool fast_approx=true) {
    return instance.get_blur_anisotropic(G,amplitude,dl,da,gauss_prec,interpolation,fast_approx);
  }

  template<typename T, typename tm> inline CImg<T> blur_anisotropic(const CImg<T>& instance, const CImg<tm>& mask,
                                                                    const float amplitude, const float sharpness=0.7f, const float anisotropy=0.3f,
                                                                    const float alpha=0.6f, const float sigma=1.1f, const float dl=0.8f,
                                                                    const float da=30.0f, const float gauss_prec=2.0f,
                                                                    const unsigned int interpolation=0, const bool fast_approx=true,
                                                                    const float geom_factor=1.0f) {
    return instance.get_blur_anisotropic(mask,amplitude,sharpness,anisotropy,alpha,sigma,dl,da,gauss_prec,interpolation,fast_approx,geom_factor);
  }

  template<typename T> inline CImg<T> blur_anisotropic(const CImg<T>& instance, const float amplitude, const float sharpness=0.7f,
						       const float anisotropy=0.3f,
						       const float alpha=0.6f, const float sigma=1.1f, const float dl=0.8f,
						       const float da=30.0f, const float gauss_prec=2.0f, const unsigned int interpolation=0,
						       const bool fast_approx=true, const float geom_factor=1.0f) {
    return instance.get_blur_anisotropic(amplitude,sharpness,anisotropy,alpha,sigma,dl,da,gauss_prec,interpolation,fast_approx,geom_factor);
  }

  template<typename T> inline CImg<T> blur_bilateral(const CImg<T>& instance,
                                                     const float sigmax, const float sigmay, const float sigmaz, const float sigmar,
                                                     const int bgridx, const int bgridy, const int bgridz, const int bgridr,
                                                     const bool interpolation=true) {
    return instance.get_blur_bilateral(sigmax,sigmay,sigmaz,sigmar,bgridx,bgridy,bgridz,bgridr,interpolation);
  }

  template<typename T> inline CImg<T> blur_bilateral(const CImg<T>& instance,
                                                     const float sigmas, const float sigmar, const int bgrids=-33, const int bgridr=32,
                                                     const bool interpolation=true) {
    return instance.get_blur_bilateral(sigmas,sigmar,bgrids,bgridr,interpolation);
  }

  template<typename T> inline CImg<T> blur_patch(const CImg<T>& instance, const unsigned int patch_size=3,
                                                 const float sigma_p=10.0f, const float sigma_s=10.0f,
                                                 const unsigned int lookup_size=4, const bool fast_approx=true) {
    return instance.get_blur_patch(patch_size,sigma_p,sigma_s,lookup_size,fast_approx);
  }

  template<typename T> inline CImgList<typename cimg::superset<T,float>::type> FFT(const CImg<T>& instance, const char axe, const bool invert=false) {
    return instance.get_FFT(axe,invert);
  }

  template<typename T> inline CImgList<typename cimg::superset<T,float>::type> FFT(const CImg<T>& instance, const bool invert=false) {
    return instance.get_FFT(invert);
  }

  template<typename T> inline CImg<T> blur_median(const CImg<T>& instance, const unsigned int n=3) {
    return instance.get_blur_median(n);
  }

  template<typename T> inline CImg<T> sharpen(const CImg<T>& instance, const float amplitude=50.0f, const float edge=1.0f,
					      const float alpha=0.0f, const float sigma=0.0f) {
    return instance.get_sharpen(amplitude,edge,alpha,sigma);
  }

  template<typename T> inline CImg<typename cimg::superset<T,float>::type>
  haar(const CImg<T>& instance, const char axis, const bool invert=false, const unsigned int nb_scales=1) {
    return instance.get_haar(axis,invert,nb_scales);
  }

  template<typename T> inline CImg<typename cimg::superset<T,float>::type>
  haar(const CImg<T>& instance, const bool invert=false, const unsigned int nb_scales=1) {
    return instance.get_haar(invert,nb_scales);
  }

  template<typename T> inline CImg<typename cimg::superset<T,float>::type>
  displacement_field(const CImg<T>& instance, const CImg<T>& target,
		     const float smooth=0.1f, const float precision=0.1f,
		     const unsigned int nb_scales=0, const unsigned int itermax=10000) {
    return instance.get_displacement_field(target,smooth,precision,nb_scales,itermax);
  }

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

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

  template<typename T> inline CImg<T> unroll(const CImg<T>& instance, const char axe='x') {
    return instance.get_unroll(axe);
  }

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

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

  template<typename T> inline CImg<T> sequence(const CImg<T>& instance, const T a0, const T a1) {
    return instance.get_sequence(a0,a1);
  }

  template<typename T> inline CImg<T> vector_at(const CImg<T>& instance, const unsigned int x=0, const unsigned int y=0, const unsigned int z=0) {
    return instance.get_vector_at(x,y,z);
  }

  template<typename T> inline CImg<T> matrix_at(const CImg<T>& instance, const unsigned int x=0, const unsigned int y=0, const unsigned int z=0) {
    return instance.get_matrix_at(x,y,z);
  }

  template<typename T> inline CImg<T> tensor_at(const CImg<T>& instance, const unsigned int x=0, const unsigned int y=0, const unsigned int z=0) {
    return instance.get_tensor_at(x,y,z);
  }

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

  template<typename T> inline CImg<typename cimg::superset<T,float>::type> invert(const CImg<T>& instance, const bool use_LU=true) {
    return instance.get_invert(use_LU);
  }

  template<typename T> inline CImg<typename cimg::superset<T,float>::type> pseudoinvert(const CImg<T>& instance) {
    return instance.get_pseudoinvert();
  }

  template<typename T, typename t> inline CImg<typename cimg::superset<T,t>::type> cross(const CImg<T>& instance, const CImg<t>& img) {
    return instance.get_cross(img);
  }

  template<typename T> inline CImgList<typename cimg::superset<T,float>::type> SVD(const CImg<T>& instance, const bool sorting=true) {
    return instance.get_SVD(sorting);
  }

  template<typename T, typename t> inline CImg<typename cimg::superset2<T,t,float>::type> solve(const CImg<T>& instance, const CImg<t>& A) {
    return instance.get_solve(A);
  }

  template<typename T> inline CImgList<typename cimg::superset<T,float>::type> eigen(const CImg<T>& instance) {
    return instance.get_eigen();
  }

  template<typename T> inline CImgList<typename cimg::superset<T,float>::type> symmetric_eigen(const CImg<T>& instance) {
    return instance.get_symmetric_eigen();
  }

  template<typename T, typename t> inline CImg<T> sort(const CImg<T>& instance, CImg<t>& permutations, const bool increasing=true) {
    return instance.get_sort(permutations,increasing);
  }

  template<typename T> inline CImg<T> sort(const CImg<T>& instance, const bool increasing=true) {
    return instance.get_sort(increasing);
  }

  template<typename T, typename t> inline CImg<T> permute(const CImg<T>& instance, const CImg<t>& permutation) {
    return instance.get_permute(permutation);
  }

  template<typename T> inline CImg<typename cimg::last<T,int>::type>
  coordinates(const CImg<T>& instance, const int coords_type, CImgDisplay &disp,
	      unsigned int *const XYZ=0, const unsigned char *const color=0) {
    return instance.get_coordinates(coords_type,disp,XYZ,color);
  }

  template<typename T, typename t> inline CImgList<typename cimg::superset<T,t>::type>
  insert(const CImgList<T>& instance, const CImg<t>& img, const unsigned int pos=~0U, const bool shared=false) {
    return instance.get_insert(img,pos,shared);
  }

  template<typename T, typename t> inline CImgList<typename cimg::superset<T,t>::type>
  insert(const CImgList<T>& instance, const unsigned int n, const CImg<t>& img,
	 const unsigned int pos=~0U, const bool shared=false) {
    return instance.get_insert(n,img,pos,shared);
  }

  template<typename T, typename t> inline CImgList<typename cimg::superset<T,t>::type>
  insert(const CImgList<T>& instance, const CImgList<t>& list, const unsigned int pos=~0U, int shared=0) {
    return instance.get_insert(list,pos,shared);
  }

  template<typename T, typename t> inline CImgList<typename cimg::superset<T,t>::type>
  insert(const CImgList<T>& instance, const unsigned int n, const CImgList<t>& list, const unsigned int pos=~0U, const int shared=0) {
    return instance.insert(n,list,pos,shared);
  }

  template<typename T> inline CImgList<T> remove(const CImgList<T>& instance, const unsigned int pos) {
    return instance.get_remove(pos);
  }

  template<typename T> inline CImgList<T> remove(const CImgList<T>& instance) {
    return instance.get_remove();
  }

  template<typename T> inline CImgList<T> reverse(const CImgList<T>& instance) {
    return instance.get_reverse();
  }

  template<typename T> inline CImgList<T> crop(const CImgList<T>& instance, const unsigned int i0, const unsigned int i1,
					       const bool shared=false) {
    return instance.get_crop(i0,i1,shared);
  }

  template<typename T> inline CImgList<T> crop(const CImgList<T>& instance, const unsigned int i0, const unsigned int i1,
					       const int x0, const int y0, const int z0, const int v0,
					       const int x1, const int y1, const int z1, const int v1) {
    return instance.get_crop(i0,i1,x0,y0,z0,v0,x1,y1,z1,v1);
  }

  template<typename T> inline CImgList<T> crop(const CImgList<T>& instance, const unsigned int i0, const unsigned int i1,
					       const int x0, const int y0, const int z0,
					       const int x1, const int y1, const int z1) {
    return instance.get_crop(i0,i1,x0,y0,z0,x1,y1,z1);
  }

  template<typename T> inline CImgList<T> crop(const CImgList<T>& instance, const unsigned int i0, const unsigned int i1,
					       const int x0, const int y0,
					       const int x1, const int y1) {
    return instance.get_crop(i0,i1,x0,y0,x1,y1);
  }

  template<typename T> inline CImgList<T> crop(const CImgList<T>& instance, const unsigned int i0, const unsigned int i1,
					       const int x0, const int x1) {
    return instance.get_crop(i0,i1,x0,x1);
  }

  template<typename T> inline CImgList<typename cimg::superset<T,float>::type>
  FFT(const CImgList<T>& instance, const char axe, const bool invert=false) {
    return instance.get_FFT(axe,invert);
  }

  template<typename T> inline CImgList<typename cimg::superset<T,float>::type>
  FFT(const CImgList<T>& instance, const bool invert=false) {
    return instance.get_FFT(invert);
  }

  template<typename T> inline CImgList<T> split(const CImgList<T>& instance, const char axe='x') {
    return instance.get_split(axe);
  }

  template<typename T> inline CImg<T> append(const CImgList<T>& instance, const char axe='x', const char align='c') {
    return instance.get_append(axe,align);
  }

  template<typename T> inline CImgList<T> crop_font(const CImgList<T>& instance) {
    return instance.get_crop_font();
  }

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

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

  struct CImgDisplay {

    //! Width of the display
    unsigned int width;

    //! Height of the display
    unsigned int height;

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

    //! Range of events detected by the display
    unsigned int events;

    //! Display title
    char* title;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    //! Fullscreen state of the display
    bool is_fullscreen;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        \see CImg::get_append()
    **/
    template<typename T> CImgDisplay& display(const CImgList<T>& list, const char axe='x', const char align='c') {
      return display(list.get_append(axe,align));
    }

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

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

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

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

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

    //! Display a 3d object
    template<typename tp, typename tf, typename tc, typename to>
    CImgDisplay& display_object3d(const tp& points, const CImgList<tf>& primitives,
                                  const CImgList<tc>& colors, const to& opacities,
                                  const bool centering=true,
                                  const int render_static=4, const int render_motion=1,
                                  const bool double_sided=false, const float focale=500.0f,
                                  const float specular_light=0.2f, const float specular_shine=0.1f,
                                  const bool display_axes=true, float *const pose_matrix=0) {
      CImg<tc>(width,height,1,3,0).display_object3d(points,primitives,colors,opacities,*this,
                                                    centering,render_static,render_motion,
                                                    double_sided,focale,specular_light,specular_shine,
                                                    display_axes,pose_matrix);
      return *this;
    }

    //! Display a 3D object.
    template<typename tp, typename tf, typename tc>
    CImgDisplay& display_object3d(const tp& points, const CImgList<tf>& primitives,
                                  const CImgList<tc>& colors,
                                  const bool centering=true,
                                  const int render_static=4, const int render_motion=1,
                                  const bool double_sided=false, const float focale=500.0f,
                                  const float specular_light=0.2f, const float specular_shine=0.1f,
                                  const bool display_axes=true,
                                  float *const pose_matrix=0,
                                  const float opacity=1.0f) {
      typedef typename cimg::superset<tc,float>::type to;
      CImg<tc>(width,height,1,3,0).display_object3d(points,primitives,colors,
                                                    CImg<to>::vector((to)opacity),
                                                    *this,centering,render_static,render_motion,
                                                    double_sided,focale,specular_light,specular_shine,display_axes,pose_matrix);
      return *this;
    }

    //! Toggle fullscreen mode
    CImgDisplay& toggle_fullscreen() {
      return assign(width,height,title,normalization,events,!is_fullscreen,is_closed);
    }

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

    //! Clear mouse and key states of the current display.
    CImgDisplay& flush() {
      std::memset((void*)buttons,0,512*sizeof(unsigned int));
      std::memset((void*)keys,0,512*sizeof(unsigned int));
      std::memset((void*)released_keys,0,512*sizeof(unsigned int));
      mouse_x = mouse_y = -1;
      return *this;
    }

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

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

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

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

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

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

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

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

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

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

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

    // Find the good width and height of a window to display an image (internal routine)
    template<typename t> static void _fitscreen(t& W, t& H) {
      unsigned int imgw = (unsigned int)W, imgh = (unsigned int)H;
      const unsigned int
        dispw = CImgDisplay::screen_dimx(), disph = CImgDisplay::screen_dimy(),
        dispmw = dispw/8, dispmh = disph/8, dispMw = dispw*15/16, dispMh = disph*15/16;
      if (imgw<dispmw) { imgh = imgh*dispmw/imgw; imgh+=(imgh==0); imgw = dispmw; }
      if (imgh<dispmh) { imgw = imgw*dispmh/imgh; imgw+=(imgw==0); imgh = dispmh; }
      if (imgw>dispMw) { imgh = imgh*dispMw/imgw; imgh+=(imgh==0); imgw = dispMw; }
      if (imgh>dispMh) { imgw = imgw*dispMh/imgh; imgw+=(imgw==0); imgh = dispMh; }
      W = (unsigned int)cimg::max(16U,imgw); H = (unsigned int)cimg::max(16U,imgh);
    }

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

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

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

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

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

    //! In-place version of the previous constructor
    template<typename T> CImgDisplay& assign(const CImg<T>& img, const char *title=0,
                                             const unsigned int normalization_type=3, const unsigned int events_type=3,
                                             const bool fullscreen_flag=false, const bool closed_flag=false) {
      fps_timer = 0*(unsigned long)(img.width + title + normalization_type + events_type + (int)fullscreen_flag + (int)closed_flag);
      return assign(0,0);
    }

    //! In-place version of the previous constructor
    template<typename T> CImgDisplay& assign(const CImgList<T>& list, const char *title=0,
                                             const unsigned int normalization_type=3, const unsigned int events_type=3,
                                             const bool fullscreen_flag=false, const bool closed_flag=false) {
      fps_timer = 0*(unsigned long)(list.size + title + normalization_type + events_type + (int)fullscreen_flag + (int)closed_flag);
      return assign(0,0);
    }

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

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

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

    //! Move window
    CImgDisplay& move(const int posx, const int posy) {
      fps_timer = 0*posx*posy;
      return *this;
    }

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

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

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

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

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

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

    //! Set the window title
    CImgDisplay& set_title(const char *format, ...) {
      fps_timer = 0*(unsigned long)format;
      return *this;
    }

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

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

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

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

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

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

    CImgDisplay& assign() {
      if (!is_empty()) {
        XLockDisplay(cimg::X11attr().display);

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

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

        // Reset display variables
        if (title) delete[] title;
        width = height = normalization = events = 0;
        is_fullscreen = is_resized = is_moved = is_event = false;
        is_closed = true;
        title = 0;
        window_x = window_y = window_width = window_height = mouse_x = mouse_y = wheel = 0;
        std::memset((void*)buttons,0,512*sizeof(unsigned int));
        std::memset((void*)keys,0,512*sizeof(unsigned int));
        std::memset((void*)released_keys,0,512*sizeof(unsigned int));
        min = max = 0;

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

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

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

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

    template<typename T> CImgDisplay& assign(const CImg<T>& img, const char *title=0,
                                             const unsigned int normalization_type=3, const unsigned int events_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,events_type,fullscreen_flag,closed_flag);
      if (normalization==2) min = (float)nimg.minmax(max);
      return render(nimg).paint();
    }

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

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

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

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

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

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

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

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

    static void wait_all() {
      if (cimg::X11attr().display) {
        XLockDisplay(cimg::X11attr().display);
        bool flag = true;
        XEvent event;
        while (flag) {
          for (unsigned int i = 0; i<cimg::X11attr().nb_wins; ++i) {
            cimg::X11attr().wins[i]->is_event = false;
            const unsigned int
              xevent_type = (cimg::X11attr().wins[i]->events)&3,
              emask = ((xevent_type>=1)?ExposureMask|StructureNotifyMask:0) |
                      ((xevent_type>=2)?ButtonPressMask|KeyPressMask|PointerMotionMask|LeaveWindowMask:0) |
                      ((xevent_type>=3)?ButtonReleaseMask|KeyReleaseMask:0);
            XSelectInput(cimg::X11attr().display,cimg::X11attr().wins[i]->window,emask);
          }
          XNextEvent(cimg::X11attr().display, &event);
          for (unsigned int i = 0; i<cimg::X11attr().nb_wins; ++i)
            if (!cimg::X11attr().wins[i]->is_closed && event.xany.window==cimg::X11attr().wins[i]->window) {
              cimg::X11attr().wins[i]->_handle_events(&event);
              if (cimg::X11attr().wins[i]->is_event) flag = false;
            }
        }
        XUnlockDisplay(cimg::X11attr().display);
      }
    }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

      // Set display variables
      width = cimg::min(dimw,(unsigned int)screen_dimx());
      height = cimg::min(dimh,(unsigned int)screen_dimy());
      normalization = normalization_type%4;
      events = events_type%4;
      is_fullscreen = fullscreen_flag;
      title = tmp_title;
      window_x = window_y = wheel = 0;
      mouse_x = mouse_y = -1;
      std::memset((void*)buttons,0,512*sizeof(unsigned int));
      std::memset((void*)keys,0,512*sizeof(unsigned int));
      std::memset((void*)released_keys,0,512*sizeof(unsigned int));
      is_resized = is_moved = is_event = false;
      is_closed = closed_flag;
      fps_timer = fps_frames = timer = 0;
      fps_fps = 0;

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

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

      if (!is_closed) _map_window(); else { window_x = window_y = cimg::type<int>::min(); }

      if (events) {
        wm_delete_window = XInternAtom(cimg::X11attr().display, "WM_DELETE_WINDOW", False);
        wm_delete_protocol = XInternAtom(cimg::X11attr().display, "WM_PROTOCOLS", False);
        XSetWMProtocols(cimg::X11attr().display, window, &wm_delete_window, 1);
        if (is_fullscreen) XGrabKeyboard(cimg::X11attr().display, window, True, GrabModeAsync, GrabModeAsync, CurrentTime);
      }
      cimg::X11attr().wins[cimg::X11attr().nb_wins++]=this;
      XUnlockDisplay(cimg::X11attr().display);
    }

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

    void _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;
            }
        break;
      }
      XStoreColors(cimg::X11attr().display,colormap,palette,256);
    }

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

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

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

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

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

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

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

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

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

    CImgDisplay& assign() {
      if (!is_empty()) {
        DestroyWindow(window);
        if (events) TerminateThread(thread,0);
        if (data) delete[] data;
        if (title) delete[] title;
        if (is_fullscreen) _desinit_fullscreen();
        width = height = normalization = events = 0;
        is_fullscreen = is_resized = is_moved = is_event = false;
        is_closed = true;
        title = 0;
        window_x = window_y = window_width = window_height = mouse_x = mouse_y = wheel = 0;
        std::memset((void*)buttons,0,512*sizeof(unsigned int));
        std::memset((void*)keys,0,512*sizeof(unsigned int));
        std::memset((void*)released_keys,0,512*sizeof(unsigned int));
        min = max = 0;
      }
      return *this;
    }

    CImgDisplay& assign(const unsigned int dimw, const unsigned int dimh, const char *title=0,
                        const unsigned int normalization_type=3, const unsigned int events_type=3,
                        const bool fullscreen_flag=false, const bool closed_flag=false) {
      if (!dimw || !dimh) return assign();
      _assign(dimw,dimh,title,normalization_type,events_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 *title=0,
                                             const unsigned int normalization_type=3, const unsigned int events_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,events_type,fullscreen_flag,closed_flag);
      if (normalization==2) min = (float)nimg.minmax(max);
      return display(nimg);
    }

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

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

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

    CImgDisplay& resize(const int nwidth, const int nheight, const bool redraw=true) {
      if (!nwidth || !nheight) return assign();
      if (is_empty()) return assign(cimg::max(nwidth,0),cimg::max(nheight,0));
      const unsigned int
        tmpdimx=(nwidth>0)?nwidth:(-nwidth*width/100),
        tmpdimy=(nheight>0)?nheight:(-nheight*height/100),
        dimx = cimg::min(tmpdimx?tmpdimx:1,(unsigned int)screen_dimx()),
        dimy = cimg::min(tmpdimy?tmpdimy:1,(unsigned int)screen_dimy());
      const bool
        is_disp_different = (width!=dimx || height!=dimy),
        is_win_different = (window_width!=dimx || window_height!=dimy);

      if (is_disp_different || is_win_different) {
        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);
        window_width  = dimx;
        window_height = dimy;
        is_resized = false;
        if (is_disp_different) {
          unsigned int *ndata = new unsigned int[dimx*dimy];
          if (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;
        }
        if (is_fullscreen) move((screen_dimx()-width)/2,(screen_dimy()-height)/2);
        if (redraw) return paint();
      }
      return *this;
    }

    CImgDisplay& move(const int posx, const int posy) {
      if (is_empty()) return *this;
      if (!is_fullscreen) {
        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& set_mouse(const int posx, const int posy) {
      if (!is_closed && posx>=0 && posy>=0) {
        _update_window_pos();
        const int res = (int)SetCursorPos(window_x+posx,window_y+posy);
        if (res) { mouse_x = posx; mouse_y = posy; }
      }
      return *this;
    }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

      // Set display variables
      width = cimg::min(dimw,(unsigned int)screen_dimx());
      height = cimg::min(dimh,(unsigned int)screen_dimy());
      normalization = normalization_type%4;
      events = events_type%4;
      is_fullscreen = fullscreen_flag;
      title = tmp_title;
      window_x = window_y = wheel = 0;
      mouse_x = mouse_y = -1;
      std::memset((void*)buttons,0,512*sizeof(unsigned int));
      std::memset((void*)keys,0,512*sizeof(unsigned int));
      std::memset((void*)released_keys,0,512*sizeof(unsigned int));
      is_resized = is_moved = is_event = false;
      is_closed = closed_flag;
      fps_timer = fps_frames = timer = 0;
      fps_fps = 0;
      visible_cursor = true;
      mouse_tracking = false;

      if (is_fullscreen) _init_fullscreen();

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

      return *this;
    }

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

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

      if (disp->events>=3) switch(msg) {
      case WM_KEYUP:
        if (disp->key) {
          std::memmove((void*)(disp->keys+1),(void*)disp->keys,512-1);
          disp->key = 0;
        }
        if (disp->released_key) std::memmove((void*)(disp->released_keys+1),(void*)disp->released_keys,512-1);
        disp->released_key = (int)wParam;
        disp->is_event = true;
        SetEvent(cimg::Win32attr().wait_event);
        break;
      case WM_LBUTTONUP:
        std::memmove((void*)(disp->buttons+1),(void*)disp->buttons,512-1);
        disp->button&=~1U;
        disp->is_event = true;
        SetEvent(cimg::Win32attr().wait_event);
        break;
      case WM_RBUTTONUP:
        std::memmove((void*)(disp->buttons+1),(void*)disp->buttons,512-1);
        disp->button&=~2U;
        disp->is_event = true;
        SetEvent(cimg::Win32attr().wait_event);
        break;
      case WM_MBUTTONUP:
        std::memmove((void*)(disp->buttons+1),(void*)disp->buttons,512-1);
        disp->button&=~4U;
        disp->is_event = true;
        SetEvent(cimg::Win32attr().wait_event);
        break;
      case WM_SETCURSOR:
        if (disp->visible_cursor) ShowCursor(TRUE);
        else ShowCursor(FALSE);
        break;
      }
      return DefWindowProc(window,msg,wParam,lParam);
    }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    CImgDisplay& assign(const unsigned int dimw, const unsigned int dimh, const char *title=0,
                        const unsigned int normalization_type=3, const unsigned int events_type=3,
                        const bool fullscreen_flag=false, const bool closed_flag=false) {
      if (!dimw || !dimh) return assign();
      _assign(dimw,dimh,title,normalization_type,events_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 *title=0,
                                             const unsigned int normalization_type=3, const unsigned int events_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,events_type,fullscreen_flag,closed_flag);
      if (normalization==2) min = (float)nimg.minmax(max);
      return display(nimg);
    }

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

    //! In-place version of the previous constructor
    CImgDisplay& assign(const CImgDisplay &win) {
      if (!win) return assign();
      _assign(win.width,win.height,win.title,win.normalization,win.events,win.is_fullscreen,win.is_closed);
      std::memcpy(data,win.data,sizeof(unsigned int)*width*height);
      return paint();
    }

    //! Display an image in a window.
    template<typename T> CImgDisplay& display(const CImg<T>& img) {
      if (is_empty()) assign(img.width,img.height);
      return render(img).paint();
    }

    //! Resize window
    CImgDisplay& resize(const int nwidth, const int nheight, const bool redraw=true) {
      cimg::CarbonInfo& c = cimg::CarbonAttr();
      if (!nwidth || !nheight) return assign();
      if (is_empty()) return assign(cimg::max(nwidth,0),cimg::max(nheight,0));
      const unsigned int
        tmpdimx = (nwidth>0)?nwidth:(-nwidth*width/100),
        tmpdimy = (nheight>0)?nheight:(-nheight*height/100),
        dimx = cimg::min(tmpdimx?tmpdimx:1,(unsigned int)screen_dimx()),
        dimy = cimg::min(tmpdimy?tmpdimy:1,(unsigned int)screen_dimy());
      const bool
        is_disp_different = (width!=dimx || height!=dimy),
        is_win_different = (window_width!=dimx || window_height!=dimy);

      if (is_disp_different || is_win_different) {
        if (!_CbSendMsg(c,CbSerializedQuery::BuildResizeWindowQuery(this,dimx,dimy,redraw)))
          throw CImgDisplayException("CImgDisplay::resize() : Cannot resize the window associated to the current display.");
        window_width  = dimx;
        window_height = dimy;
        is_resized = false;
        if (is_disp_different) {
          unsigned int *ndata = new unsigned int[dimx*dimy];
          if (redraw) _render_resize(data,width,height,ndata,dimx,dimy);
          else std::memset(ndata,0x80,sizeof(unsigned int)*dimx*dimy);
          unsigned int* old = data;
          width = dimx;
          height = dimy;
          data = ndata;
          _CbReinitGraphics();
          delete[] old;
        }
        if (is_fullscreen) move((screen_dimx()-width)/2,(screen_dimy()-height)/2);
        if (redraw) return paint();
      }
      return *this;
    }

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

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

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

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

    //! Wait for a window event in any CImg window
    static void wait_all() {
      cimg::CarbonInfo& c = cimg::CarbonAttr();
      MPWaitOnSemaphore(c.wait_event,kDurationForever);
    }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        // Finalize graphics
        _CbFinalizeGraphics();

        // Do some cleanup
        if (data) delete[] data;
        if (title) delete[] title;
        width = height = normalization = events = 0;
        is_fullscreen = is_resized = is_moved = is_event = false;
        is_closed = true;
        title = 0;
        window_x = window_y = window_width = window_height = mouse_x = mouse_y = wheel = 0;
        std::memset((void*)buttons,0,512*sizeof(unsigned int));
        std::memset((void*)keys,0,512*sizeof(unsigned int));
        std::memset((void*)released_keys,0,512*sizeof(unsigned int));
        min = max = 0;
        if (MPDeleteCriticalRegion(paintCriticalRegion)!=noErr)
          throw CImgDisplayException("CImgDisplay()::assign() : MPDeleteCriticalRegion failed.");
      }
      return *this;
    }

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

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

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

      // Set display variables
      width = cimg::min(dimw,(unsigned int)screen_dimx());
      height = cimg::min(dimh,(unsigned int)screen_dimy());
      normalization = normalization_type%4;
      events = events_type%4;
      is_fullscreen = fullscreen_flag;
      title = tmp_title;
      window_x = window_y = wheel = 0;
      mouse_x = mouse_y = -1;
      std::memset((void*)buttons,0,512*sizeof(unsigned int));
      std::memset((void*)keys,0,512*sizeof(unsigned int));
      std::memset((void*)released_keys,0,512*sizeof(unsigned int));
      is_resized = is_moved = is_event = false;
      is_closed = closed_flag;
      fps_timer = fps_frames = timer = 0;
      fps_fps = 0;
      lastKeyModifiers = 0;

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

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

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

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

#endif

  };

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

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

     \par Image representation

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

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

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

     \par Image structure

     The \ref CImg<\c T> structure contains \a five fields :
     - \ref width defines the number of \a columns of the image (size along the X-axis).
     - \ref height defines the number of \a rows of the image (size along the Y-axis).
     - \ref depth defines the number of \a slices of the image (size along the Z-axis).
     - \ref dim defines the number of \a channels of the image (size along the V-axis).
     - \ref data defines a \a pointer to the \a pixel \a data (of type \c T).

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

     \par Image declaration and construction

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

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

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

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

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

     \par Most useful functions

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

     - operator()(), operator[]() : allows to access or write pixel values.
     - display() : displays the image in a new window.

     \sa CImgList, CImgDisplay, CImgException.

  **/
  template<typename T> struct CImg {

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

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

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

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

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

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

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

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

    //! Get value type
    typedef T value_type;

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

    //@}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    //! Return the number of slices of the instance image (size along the Z-axis).
    /**
       \sa depth, dimx(), dimy(), dimv(), size().
    **/
    int dimz() const {
      return (int)depth;
    }

    //! Return the number of vector channels of the instance image (size along the V-axis).
    /**
       \sa dim, dimx(), dimy(), dimz(), size().
    **/
    int dimv() const {
      return (int)dim;
    }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    //! Return \c true if specified pixel is inside image boundaries. If true, returns pixel coordinates in (x,y,z).
    template<typename t> bool contains(const T& pixel, t& x, t& y, t& z) const {
      t v;
      return contains(&pixel,x,y,z,v);
    }

    //! Return \c true if specified pixel is inside image boundaries. If true, returns pixel coordinates in (x,y).
    template<typename t> bool contains(const T& pixel, t& x, t& y) const {
      t z,v;
      return contains(&pixel,x,y,z,v);
    }

    //! Return \c true if specified pixel is inside image boundaries. If true, returns pixel coordinates in (x).
    template<typename t> bool contains(const T& pixel, t& x) const {
      t y,z,v;
      return contains(&pixel,x,y,z,v);
    }

    //! Return \c true if pixel is inside the image boundaries.
    bool contains(const T& pixel) const {
      unsigned int x,y,z,v;
      return contains(&pixel,x,y,z,v);
    }

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

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

    //! Image to boolean conversion
    operator bool() const {
      return !is_empty();
    }

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

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

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

    long offset(const int x, const int y, const int z) const {
      return (long)x + (long)y*width + (long)z*width*height;
    }

    long offset(const int x, const int y) const {
      return (long)x + (long)y*width;
    }

    long offset(const int x) const {
      return (long)x;
    }

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

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

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

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

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

    T* ptr(const unsigned int x, const unsigned int y, const unsigned int z) {
      return data + (long)x + (long)y*width + (long)z*width*height;
    }

    const T* ptr(const unsigned int x, const unsigned int y, const unsigned int z) const {
      return data + (long)x + (long)y*width + (long)z*width*height;
    }

    T* ptr(const unsigned int x, const unsigned int y) {
      return data + (long)x + (long)y*width;
    }

    const T* ptr(const unsigned int x, const unsigned int y) const {
      return data + (long)x + (long)y*width;
    }

    T* ptr(const unsigned int x) {
      return data + (long)x;
    }

    const T* ptr(const unsigned int x) const {
      return data + (long)x;
    }
#endif
    T* ptr() {
      return data;
    }

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

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

    const_iterator begin() const {
      return data;
    }

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

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

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

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

       \par example:
       \code
       CImg<float> img(100,100,1,3,0);                       // Define a 100x100 color image with float-valued black pixels.
       const float valR = img(10,10,0,0);                    // Read the red component at coordinates (10,10).
       const float valG = img(10,10,0,1);                    // Read the green component at coordinates (10,10)
       const float valB = img(10,10,2);                      // Read the blue component at coordinates (10,10) (Z-coordinate omitted here).
       const float avg = (valR + valG + valB)/3;             // Compute average pixel value.
       img(10,10,0) = img(10,10,1) = img(10,10,2) = avg;     // Replace the pixel (10,10) by the average grey value.
       \endcode

       \sa operator[](), ptr(), offset(), cimg_storage, cimg_environment.
    **/
#if cimg_debug>=3
    T& operator()(const unsigned int x, const unsigned int y=0, const unsigned int z=0, const unsigned int v=0) {
      const long off = offset(x,y,z,v);
      if (!data || off>=(long)size()) {
        cimg::warn("CImg<%s>::operator() : Pixel access requested at (%u,%u,%u,%u) (offset=%ld) "
                   "outside the image range (%u,%u,%u,%u) (size=%lu)",
                   pixel_type(),x,y,z,v,off,width,height,depth,dim,size());
        return *data;
      }
      else return data[off];
    }

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

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

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

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

    T& operator()(const unsigned int x, const unsigned int y) {
      return data[(long)x + (long)y*width];
    }

    const T& operator()(const unsigned int x, const unsigned int y) const {
      return data[(long)x + (long)y*width];
    }

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

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

    //! Return pixel value at a given position. Equivalent to operator().
    T& at(const unsigned int x, const unsigned int y=0, const unsigned int z=0, const unsigned int v=0) {
      return (*this)(x,y,z,v);
    }

    const T& at(const unsigned int x, const unsigned int y=0, const unsigned int z=0, const unsigned int v=0) const {
      return (*this)(x,y,z,v);
    }

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

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

       \par example:
       \code
       CImg<float> vec(1,10);        // Define a vector of float values (10 lines, 1 row).
       const float val1 = vec(0,4);  // Get the fifth element using operator()().
       const float val2 = vec[4];    // Get the fifth element using operator[]. Here, val2==val1.
       \endcode

       \sa operator()(), ptr(), offset(), cimg_storage, cimg_environment.
    **/
#if cimg_debug>=3
    T& operator[](const unsigned long off) {
      if (!data || off>=size()) {
        cimg::warn("CImg<%s>::operator[] : Pixel access requested at offset=%lu "
                   "outside the image range (%u,%u,%u,%u) (size=%lu)",
                   pixel_type(),off,width,height,depth,dim,size());
        return *data;
      }
      else return data[off];
    }

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

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

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

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

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

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

    //! Read a pixel value with Dirichlet or Neumann boundary conditions.
    /**
       \param x X-coordinate of the pixel.
       \param y Y-coordinate of the pixel.
       \param z Z-coordinate of the pixel.
       \param v V-coordinate of the pixel.
       \param out_val Desired value if pixel coordinates are outside the image range (optional parameter).

       - This function allows to read pixel values with boundary checking on all coordinates.
       - If given coordinates are outside the image range and the parameter out_val is specified, the value \c out_val is returned.
       - If given coordinates are outside the image range and the parameter out_val is not specified, the closest pixel value
       is returned.

       \par example:
       \code
       CImg<float> img(100,100,1,1,128);           // Define a 100x100 images with all pixel values equal to 128.
       const float val1 = img.pix4d(10,10,0,0,0);  // Equivalent to val1=img(10,10) (but slower).
       const float val2 = img.pix4d(-4,5,0,0,0);   // Return 0, since coordinates are outside the image range.
       const float val3 = img.pix4d(10,10,5,0,64); // Return 64, since coordinates are outside the image range.
       \endcode

       \sa operator()(), linear_pix4d(), cubic_pix2d().
    **/
    T pix1d(const int x, const int y, const int z, const int v, const T out_val) const {
      return (x<0 || x>=dimx())?out_val:(*this)(x,y,z,v);
    }

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

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

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

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

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

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

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

    //! Read a pixel value using linear interpolation for the first coordinate \c cx.
    /**
       - Same as linear_pix4d(), except that linear interpolation and boundary checking is performed only on the first coordinate.

       \sa operator()(), linear_pix4d(), linear_pix3d(), linear_pix2d(), linear_pix1d(), cubic_pix1d().
    **/
    typename cimg::superset<T,float>::type linear_pix1d(const float fx, const int y, const int z, const int v,
                                                        const T out_val) const {
      typedef typename cimg::superset<T,float>::type ftype;
      const int
        x = (int)fx-(fx>=0?0:1), nx = x+1;
      const float
        dx = fx-x;
      const ftype
        Ic = (ftype)pix1d(x,y,z,v,out_val), In = (ftype)pix2d(nx,y,z,v,out_val);
      return Ic + dx*(In-Ic);
    }

    typename cimg::superset<T,float>::type linear_pix1d(const float fx, const int y=0, const int z=0, const int v=0) const {
      typedef typename cimg::superset<T,float>::type ftype;
      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 ftype
        Ic = (ftype)(*this)(x,y,z,v), In = (ftype)(*this)(nx,y,z,v);
      return Ic + dx*(In-Ic);
    }

    //! Read a pixel value using linear interpolation for the two first coordinates (\c cx,\c cy).
    /**
       - Same as linear_pix4d(), except that linear interpolation and boundary checking is performed only on the two first coordinates.

       \sa operator()(), linear_pix4d(), linear_pix3d(), linear_pix1d(), linear_pix2d(), cubic_pix2d().
    **/
    typename cimg::superset<T,float>::type linear_pix2d(const float fx, const float fy, const int z, const int v,
                                                        const T out_val) const {
      typedef typename cimg::superset<T,float>::type ftype;
      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 ftype
        Icc = (ftype)pix2d(x,y,z,v,out_val),  Inc = (ftype)pix2d(nx,y,z,v,out_val),
        Icn = (ftype)pix2d(x,ny,z,v,out_val), Inn = (ftype)pix2d(nx,ny,z,v,out_val);
      return Icc + dx*(Inc-Icc + dy*(Icc+Inn-Icn-Inc)) + dy*(Icn-Icc);
    }

    typename cimg::superset<T,float>::type linear_pix2d(const float fx, const float fy, const int z=0, const int v=0) const {
      typedef typename cimg::superset<T,float>::type ftype;
      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 ftype
        Icc = (ftype)(*this)(x,y,z,v),  Inc = (ftype)(*this)(nx,y,z,v),
        Icn = (ftype)(*this)(x,ny,z,v), Inn = (ftype)(*this)(nx,ny,z,v);
      return Icc + dx*(Inc-Icc + dy*(Icc+Inn-Icn-Inc)) + dy*(Icn-Icc);
    }

    //! Read a pixel value using linear interpolation for the three first coordinates (\c cx,\c cy,\c cz).
    /**
       - Same as linear_pix4d(), except that linear interpolation and boundary checking is performed only on the three first coordinates.

       \sa operator()(), linear_pix4d(), linear_pix2d(), linear_pix1d(), linear_pix3d(), cubic_pix2d().
    **/
    typename cimg::superset<T,float>::type linear_pix3d(const float fx, const float fy, const float fz, const int v,
                                                        const T out_val) const {
      typedef typename cimg::superset<T,float>::type ftype;
      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 ftype
        Iccc = (ftype)pix3d(x,y,z,v,out_val), Incc = (ftype)pix3d(nx,y,z,v,out_val),
        Icnc = (ftype)pix3d(x,ny,z,v,out_val), Innc = (ftype)pix3d(nx,ny,z,v,out_val),
        Iccn = (ftype)pix3d(x,y,nz,v,out_val), Incn = (ftype)pix3d(nx,y,nz,v,out_val),
        Icnn = (ftype)pix3d(x,ny,nz,v,out_val), Innn = (ftype)pix3d(nx,ny,nz,v,out_val);
      return Iccc +
        dx*(Incc-Iccc +
            dy*(Iccc+Innc-Icnc-Incc +
                dz*(Iccn+Innn+Icnc+Incc-Icnn-Incn-Iccc-Innc)) +
            dz*(Iccc+Incn-Iccn-Incc)) +
        dy*(Icnc-Iccc +
            dz*(Iccc+Icnn-Iccn-Icnc)) +
        dz*(Iccn-Iccc);
    }

    typename cimg::superset<T,float>::type linear_pix3d(const float fx, const float fy=0, const float fz=0, const int v=0) const {
      typedef typename cimg::superset<T,float>::type ftype;
      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 ftype
        Iccc = (ftype)(*this)(x,y,z,v), Incc = (ftype)(*this)(nx,y,z,v),
        Icnc = (ftype)(*this)(x,ny,z,v), Innc = (ftype)(*this)(nx,ny,z,v),
        Iccn = (ftype)(*this)(x,y,nz,v), Incn = (ftype)(*this)(nx,y,nz,v),
        Icnn = (ftype)(*this)(x,ny,nz,v), Innn = (ftype)(*this)(nx,ny,nz,v);
      return Iccc +
        dx*(Incc-Iccc +
            dy*(Iccc+Innc-Icnc-Incc +
                dz*(Iccn+Innn+Icnc+Incc-Icnn-Incn-Iccc-Innc)) +
            dz*(Iccc+Incn-Iccn-Incc)) +
        dy*(Icnc-Iccc +
            dz*(Iccc+Icnn-Iccn-Icnc)) +
        dz*(Iccn-Iccc);
    }

    //! Read a pixel value using linear interpolation.
    /**
       \param fx X-coordinate of the pixel (float-valued).
       \param fy Y-coordinate of the pixel (float-valued).
       \param fz Z-coordinate of the pixel (float-valued).
       \param fv V-coordinate of the pixel (float-valued).
       \param out_val Out-of-border pixel value

       - This function allows to read pixel values with boundary checking on all coordinates.
       - If given coordinates are outside the image range, the value of the nearest pixel inside the image is returned
       (Neumann boundary conditions).
       - If given coordinates are float-valued, a linear interpolation is performed in order to compute the returned value.

       \par example:
       \code
       CImg<float> img(2,2);     // Define a greyscale 2x2 image.
       img(0,0) = 0;             // Fill image with specified pixel values.
       img(1,0) = 1;
       img(0,1) = 2;
       img(1,1) = 3;
       const double val = img.linear_pix4d(0.5,0.5);  // Return val=1.5, which is the average intensity of the four pixels values.
       \endcode

       \sa operator()(), linear_pix3d(), linear_pix2d(), linear_pix1d(), cubic_pix2d().
    **/
    typename cimg::superset<T,float>::type linear_pix4d(const float fx, const float fy, const float fz, const float fv,
                                                        const T out_val) const {
      typedef typename cimg::superset<T,float>::type ftype;
      const int
        x = (int)fx-(fx>=0?0:1), nx = x+1,
        y = (int)fy-(fy>=0?0:1), ny = y+1,
        z = (int)fz-(fz>=0?0:1), nz = z+1,
        v = (int)fv-(fv>=0?0:1), nv = v+1;
      const float
        dx = fx-x,
        dy = fy-y,
        dz = fz-z,
        dv = fv-v;
      const ftype
        Icccc = (ftype)pix4d(x,y,z,v,out_val), Inccc = (ftype)pix4d(nx,y,z,v,out_val),
        Icncc = (ftype)pix4d(x,ny,z,v,out_val), Inncc = (ftype)pix4d(nx,ny,z,v,out_val),
        Iccnc = (ftype)pix4d(x,y,nz,v,out_val), Incnc = (ftype)pix4d(nx,y,nz,v,out_val),
        Icnnc = (ftype)pix4d(x,ny,nz,v,out_val), Innnc = (ftype)pix4d(nx,ny,nz,v,out_val),
        Icccn = (ftype)pix4d(x,y,z,nv,out_val), Inccn = (ftype)pix4d(nx,y,z,nv,out_val),
        Icncn = (ftype)pix4d(x,ny,z,nv,out_val), Inncn = (ftype)pix4d(nx,ny,z,nv,out_val),
        Iccnn = (ftype)pix4d(x,y,nz,nv,out_val), Incnn = (ftype)pix4d(nx,y,nz,nv,out_val),
        Icnnn = (ftype)pix4d(x,ny,nz,nv,out_val), Innnn = (ftype)pix4d(nx,ny,nz,nv,out_val);
      return Icccc +
        dx*(Inccc-Icccc +
            dy*(Icccc+Inncc-Icncc-Inccc +
                dz*(Iccnc+Innnc+Icncc+Inccc-Icnnc-Incnc-Icccc-Inncc +
                    dv*(Iccnn+Innnn+Icncn+Inccn+Icnnc+Incnc+Icccc+Inncc-Icnnn-Incnn-Icccn-Inncn-Iccnc-Innnc-Icncc-Inccc)) +
                dv*(Icccn+Inncn+Icncc+Inccc-Icncn-Inccn-Icccc-Inncc)) +
            dz*(Icccc+Incnc-Iccnc-Inccc +
                dv*(Icccn+Incnn+Iccnc+Inccc-Iccnn-Inccn-Icccc-Incnc)) +
            dv*(Icccc+Inccn-Inccc-Icccn)) +
        dy*(Icncc-Icccc +
            dz*(Icccc+Icnnc-Iccnc-Icncc +
                dv*(Icccn+Icnnn+Iccnc+Icncc-Iccnn-Icncn-Icccc-Icnnc)) +
            dv*(Icccc+Icncn-Icncc-Icccn)) +
        dz*(Iccnc-Icccc +
            dv*(Icccc+Iccnn-Iccnc-Icccn)) +
        dv*(Icccn-Icccc);
    }

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

    //! Read a pixel value using cubic interpolation for the first coordinate \c cx.
    /**
       - Same as cubic_pix2d(), except that cubic interpolation and boundary checking is performed only on the first coordinate.

       \sa operator()(), cubic_pix2d(), linear_pix1d().
    **/
    typename cimg::superset<T,float>::type cubic_pix1d(const float fx, const int y, const int z, const int v,
                                                       const T out_val) const {
      typedef typename cimg::superset<T,float>::type ftype;
      const int
        x = (int)fx-(fx>=0?0:1), px = x-1, nx = x+1, ax = x+2;
      const float
        dx = fx-x;
      const ftype
        Ip = (ftype)pix1d(px,y,z,v,out_val), Ic = (ftype)pix1d(x,y,z,v,out_val),
        In = (ftype)pix1d(nx,y,z,v,out_val), Ia = (ftype)pix1d(ax,y,z,v,out_val),
        valm = cimg::min(Ip,In,Ic,Ia), valM = cimg::max(Ip,In,Ic,Ia),
        u0 = Ic - Ip,
        u1 = Ia - In,
        a = 2*(Ic-In) + u0 + u1,
        b = 3*(In-Ic) - 2*u0 - u1,
        val = a*dx*dx*dx + b*dx*dx + u0*dx + Ic;
      return val<valm?valm:(val>valM?valM:val);
    }

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

    //! Read a pixel value using bicubic interpolation.
    /**
       \param fx X-coordinate of the pixel (float-valued).
       \param fy Y-coordinate of the pixel (float-valued).
       \param z Z-coordinate of the pixel.
       \param v V-coordinate of the pixel.
       \param out_val Value considered at image borders.

       - This function allows to read pixel values with boundary checking on the two first coordinates.
       - If given coordinates are outside the image range, the value of the nearest pixel inside the image is returned
       (Neumann boundary conditions).
       - If given coordinates are float-valued, a cubic interpolation is performed in order to compute the returned value.

       \sa operator()(), cubic_pix1d(), linear_pix2d().
    **/
    typename cimg::superset<T,float>::type cubic_pix2d(const float fx, const float fy, const int z, const int v,
                                                       const T out_val) const {
      typedef typename cimg::superset<T,float>::type ftype;
      const int
        x = (int)fx-(fx>=0?0:1), px = x-1, nx = x+1, ax = x+2,
        y = (int)fy-(fy>=0?0:1), py = y-1, ny = y+1, ay = y+2;
      const float
        dx = fx-x, dx2 = dx*dx, dx3 = dx2*dx,
        dy = fy-y;
      const ftype
        Ipp = (ftype)pix2d(px,py,z,v,out_val), Icp = (ftype)pix2d(x,py,z,v,out_val),
        Inp = (ftype)pix2d(nx,py,z,v,out_val), Iap = (ftype)pix2d(ax,py,z,v,out_val),
        Ipc = (ftype)pix2d(px,y,z,v,out_val),  Icc = (ftype)pix2d(x,y,z,v,out_val),
        Inc = (ftype)pix2d(nx,y,z,v,out_val),  Iac = (ftype)pix2d(ax,y,z,v,out_val),
        Ipn = (ftype)pix2d(px,ny,z,v,out_val), Icn = (ftype)pix2d(x,ny,z,v,out_val),
        Inn = (ftype)pix2d(nx,ny,z,v,out_val), Ian = (ftype)pix2d(ax,ny,z,v,out_val),
        Ipa = (ftype)pix2d(px,ay,z,v,out_val), Ica = (ftype)pix2d(x,ay,z,v,out_val),
        Ina = (ftype)pix2d(nx,ay,z,v,out_val), Iaa = (ftype)pix2d(ax,ay,z,v,out_val),
        valm = cimg::min(cimg::min(Ipp,Icp,Inp,Iap),cimg::min(Ipc,Icc,Inc,Iac),cimg::min(Ipn,Icn,Inn,Ian),cimg::min(Ipa,Ica,Ina,Iaa)),
        valM = cimg::max(cimg::max(Ipp,Icp,Inp,Iap),cimg::max(Ipc,Icc,Inc,Iac),cimg::max(Ipn,Icn,Inn,Ian),cimg::max(Ipa,Ica,Ina,Iaa)),
        u0p = Icp - Ipp,
        u1p = Iap - Inp,
        ap = 2*(Icp-Inp) + u0p + u1p,
        bp = 3*(Inp-Icp) - 2*u0p - u1p,
        u0c = Icc - Ipc,
        u1c = Iac - Inc,
        ac = 2*(Icc-Inc) + u0c + u1c,
        bc = 3*(Inc-Icc) - 2*u0c - u1c,
        u0n = Icn - Ipn,
        u1n = Ian - Inn,
        an = 2*(Icn-Inn) + u0n + u1n,
        bn = 3*(Inn-Icn) - 2*u0n - u1n,
        u0a = Ica - Ipa,
        u1a = Iaa - Ina,
        aa = 2*(Ica-Ina) + u0a + u1a,
        ba = 3*(Ina-Ica) - 2*u0a - u1a,
        valp = ap*dx3 + bp*dx2 + u0p*dx + Icp,
        valc = ac*dx3 + bc*dx2 + u0c*dx + Icc,
        valn = an*dx3 + bn*dx2 + u0n*dx + Icn,
        vala = aa*dx3 + ba*dx2 + u0a*dx + Ica,
        u0 = valc - valp,
        u1 = vala - valn,
        a = 2*(valc-valn) + u0 + u1,
        b = 3*(valn-valc) - 2*u0 - u1,
        val = a*dy*dy*dy + b*dy*dy + u0*dy + valc;
      return val<valm?valm:(val>valM?valM:val);
    }

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

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

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

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

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

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

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

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

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

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

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

    //! Return the variance and the mean of the image
    double variance(const unsigned int variance_method=0) const {
      double foo;
      return variancemean(variance_method,foo);
    }

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

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

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

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

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

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

    //! Return the determinant of the current matrix.
    double det() const {
      if (is_empty() || width!=height || depth!=1 || dim!=1)
        throw CImgInstanceException("CImg<%s>::det() : Instance matrix (%u,%u,%u,%u,%p) is not square or is empty.",
                                    pixel_type(),width,height,depth,dim,data);
      switch (width) {
      case 1: return (*this)(0,0);
      case 2: return (*this)(0,0)*(*this)(1,1)-(*this)(0,1)*(*this)(1,0);
      case 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];
        return i*a*e-a*h*f-i*b*d+b*g*f+c*d*h-c*g*e;
      }
      default: {
        typedef typename cimg::superset<T,float>::type ftype;
        CImg<ftype> lu(*this);
        CImg<unsigned int> indx;
        bool d;
        lu._LU(indx,d);
        double res = d?1.0:-1.0;
        cimg_forX(lu,i) res*=lu(i,i);
        return res;
      }
      }
      return 0;
    }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    //! In-place version of the previous function.
    template<typename t> CImg<T>& apply(t& func) {
      cimg_for(*this,ptr,T) *ptr = func(*ptr);
      return *this;
    }

    //! Pointwise multiplication between two images.
    template<typename t> CImg<typename cimg::superset<T,t>::type> get_mul(const CImg<t>& img) const {
      typedef typename cimg::superset<T,t>::type restype;
      return CImg<restype>(*this,false).mul(img);
    }

    //! In-place version of the previous function.
    template<typename t> CImg<T>& mul(const CImg<t>& img) {
      if (is_overlapping(img)) return mul(+img);
      t *ptrs = img.data;
      T *ptrf = data + cimg::min(size(),img.size());
      for (T* ptrd = data; ptrd<ptrf; ++ptrd) (*ptrd) = (T)(*ptrd*(*(ptrs++)));
      return *this;
    }

    //! Pointwise division between two images.
    template<typename t> CImg<typename cimg::superset<T,t>::type> get_div(const CImg<t>& img) const {
      typedef typename cimg::superset<T,t>::type restype;
      return CImg<restype>(*this,false).div(img);
    }

    //! In-place version of the previous function.
    template<typename t> CImg<T>& div(const CImg<t>& img) {
      if (is_overlapping(img)) return div(+img);
      t *ptrs = img.data;
      T *ptrf = data + cimg::min(size(),img.size());
      for (T* ptrd = data; ptrd<ptrf; ++ptrd) (*ptrd) = (T)(*ptrd/(*(ptrs++)));
      return *this;
    }

    //! Pointwise max operator between two images.
    template<typename t> CImg<typename cimg::superset<T,t>::type> get_max(const CImg<t>& img) const {
      typedef typename cimg::superset<T,t>::type restype;
      return CImg<restype>(*this,false).max(img);
    }

    //! In-place version of the previous function.
    template<typename t> CImg<T>& max(const CImg<t>& img) {
      if (is_overlapping(img)) return max(+img);
      t *ptrs = img.data;
      T *ptrf = data + cimg::min(size(),img.size());
      for (T* ptrd = data; ptrd<ptrf; ++ptrd) (*ptrd) = cimg::max((T)*(ptrs++),*ptrd);
      return *this;
    }

    //! Pointwise max operator between an image and a value.
    CImg<T> get_max(const T val) const {
      return (+*this).max(val);
    }

    //! In-place version of the previous function.
    CImg<T>& max(const T val) {
      cimg_for(*this,ptr,T) (*ptr) = cimg::max(*ptr,val);
      return *this;
    }

    //! Pointwise min operator between two images.
    template<typename t> CImg<typename cimg::superset<T,t>::type> get_min(const CImg<t>& img) const {
      typedef typename cimg::superset<T,t>::type restype;
      return CImg<restype>(*this,false).min(img);
    }

    //! In-place version of the previous function.
    template<typename t> CImg<T>& min(const CImg<t>& img) {
      if (is_overlapping(img)) return min(+img);
      t *ptrs = img.data;
      T *ptrf = data + cimg::min(size(),img.size());
      for (T* ptrd = data; ptrd<ptrf; ++ptrd) (*ptrd) = cimg::min((T)*(ptrs++),*ptrd);
      return *this;
    }

    //! Pointwise min operator between an image and a value.
    CImg<T> get_min(const T val) const {
      return (+*this).min(val);
    }

    //! In-place version of the previous function.
    CImg<T>& min(const T val) {
      cimg_for(*this,ptr,T) (*ptr) = cimg::min(*ptr,val);
      return *this;
    }

    //! Compute a statistics vector (min,max,mean,variance,offmin,offmax).
    CImg<typename cimg::last<T,double>::type> get_stats() const {
      typedef typename cimg::last<T,double>::type restype;
      return CImg<restype>(*this,false).stats();
    }

    //! In-place version of the previous function.
    CImg<T>& stats() {
      if (is_empty())
        throw CImgInstanceException("CImg<%s>::stats() : Instance image is empty.",pixel_type());
      const unsigned long siz = size();
      const T *const odata = data;
      const T *pm = odata, *pM = odata;
      double S = 0, S2 = 0;
      T m = *pm, M = m;
      cimg_for(*this,ptr,T) {
        const T val = *ptr;
        const double fval = (double)val;
        if (val<m) { m = val; pm = ptr; }
        if (val>M) { M = val; pM = ptr; }
        S+=fval;
        S2+=fval*fval;
      }
      return assign(1,6).fill((T)m,(T)M,(T)(S/siz),(T)((S2-S*S/siz)/siz),(T)(pm-odata),(T)(pM-odata));
    }

    //! Compute the square of each pixel value.
    CImg<typename cimg::superset<T,float>::type> get_sqr() const {
      typedef typename cimg::superset<T,float>::type restype;
      return CImg<restype>(*this,false).sqr();
    }

    //! In-place version of the previous function.
    CImg<T>& sqr() {
      cimg_for(*this,ptr,T) { const T val = *ptr; *ptr = (T)(val*val); };
      return *this;
    }

    //! Compute the square root of each pixel value.
    CImg<typename cimg::superset<T,float>::type> get_sqrt() const {
      typedef typename cimg::superset<T,float>::type restype;
      return CImg<restype>(*this,false).sqrt();
    }

    //! In-place version of the previous function.
    CImg<T>& sqrt() {
      cimg_for(*this,ptr,T) (*ptr) = (T)std::sqrt((double)(*ptr));
      return *this;
    }

    //! Compute the exponential of each pixel value.
    CImg<typename cimg::superset<T,float>::type> get_exp() const {
      typedef typename cimg::superset<T,float>::type restype;
      return CImg<restype>(*this,false).exp();
    }

    //! In-place version of the previous function.
    CImg<T>& exp() {
      cimg_for(*this,ptr,T) (*ptr) = (T)std::exp((double)(*ptr));
      return *this;
    }

    //! Compute the log of each each pixel value.
    CImg<typename cimg::superset<T,float>::type> get_log() const {
      typedef typename cimg::superset<T,float>::type restype;
      return CImg<restype>(*this,false).log();
    }

    //! In-place version of the previous function.
    CImg<T>& log() {
      cimg_for(*this,ptr,T) (*ptr) = (T)std::log((double)(*ptr));
      return *this;
    }

    //! Compute the log10 of each each pixel value.
    CImg<typename cimg::superset<T,float>::type> get_log10() const {
      typedef typename cimg::superset<T,float>::type restype;
      return CImg<restype>(*this,false).log10();
    }

    //! In-place version of the previous function.
    CImg<T>& log10() {
      cimg_for(*this,ptr,T) (*ptr) = (T)std::log10((double)(*ptr));
      return *this;
    }

    //! Compute the power by p of each pixel value.
    CImg<typename cimg::superset<T,float>::type> get_pow(const double p) const {
      typedef typename cimg::superset<T,float>::type restype;
      return CImg<restype>(*this,false).pow(p);
    }

    //! In-place version of the previous function.
    CImg<T>& pow(const double p) {
      if (p==0) return fill(1);
      if (p==0.5) { cimg_for(*this,ptr,T) { const T val = *ptr; *ptr = (T)std::sqrt((double)val); } return *this; }
      if (p==1) return *this;
      if (p==2) { cimg_for(*this,ptr,T) { const T val = *ptr; *ptr = val*val; } return *this; }
      if (p==3) { cimg_for(*this,ptr,T) { const T val = *ptr; *ptr = val*val*val; } return *this; }
      if (p==4) { cimg_for(*this,ptr,T) { const T val = *ptr; *ptr = val*val*val*val; } return *this; }
      cimg_for(*this,ptr,T) (*ptr) = (T)std::pow((double)(*ptr),p);
      return *this;
    }

    //! Compute the power of each pixel value.
    template<typename t> CImg<typename cimg::superset<T,float>::type> get_pow(const CImg<t>& img) const {
      typedef typename cimg::superset<T,float>::type restype;
      return CImg<restype>(*this,false).pow(img);
    }

    //! In-place version of the previous function.
    template<typename t> CImg<T>& pow(const CImg<t>& img) {
      if (is_overlapping(img)) return pow(+img);
      t *ptrs = img.data;
      T *ptrf = data + cimg::min(size(),img.size());
      for (T* ptrd = data; ptrd<ptrf; ++ptrd) (*ptrd) = (T)std::pow((double)*ptrd,(double)(*(ptrs++)));
      return *this;
    }

    //! Compute the absolute value of each pixel value.
    CImg<typename cimg::superset<T,float>::type> get_abs() const {
      typedef typename cimg::superset<T,float>::type restype;
      return CImg<restype>(*this,false).abs();
    }

    //! In-place version of the previous function.
    CImg<T>& abs() {
      cimg_for(*this,ptr,T) (*ptr) = cimg::abs(*ptr);
      return *this;
    }

    //! Compute the cosinus of each pixel value.
    CImg<typename cimg::superset<T,float>::type> get_cos() const {
      typedef typename cimg::superset<T,float>::type restype;
      return CImg<restype>(*this,false).cos();
    }

    //! In-place version of the previous function.
    CImg<T>& cos() {
      cimg_for(*this,ptr,T) (*ptr) = (T)std::cos((double)(*ptr));
      return *this;
    }

    //! Compute the sinus of each pixel value.
    CImg<typename cimg::superset<T,float>::type> get_sin() const {
      typedef typename cimg::superset<T,float>::type restype;
      return CImg<restype>(*this,false).sin();
    }

    //! In-place version of the previous function.
    CImg<T>& sin() {
      cimg_for(*this,ptr,T) (*ptr) = (T)std::sin((double)(*ptr));
      return *this;
    }

    //! Compute the tangent of each pixel.
    CImg<typename cimg::superset<T,float>::type> get_tan() const {
      typedef typename cimg::superset<T,float>::type restype;
      return CImg<restype>(*this,false).tan();
    }

    //! In-place version of the previous function.
    CImg<T>& tan() {
      cimg_for(*this,ptr,T) (*ptr) = (T)std::tan((double)(*ptr));
      return *this;
    }

    //! Compute the arc-cosine of each pixel value.
    CImg<typename cimg::superset<T,float>::type> get_acos() const {
      typedef typename cimg::superset<T,float>::type restype;
      return CImg<restype>(*this,false).acos();
    }

    //! In-place version of the previous function.
    CImg<T>& acos() {
      cimg_for(*this,ptr,T) (*ptr) = (T)std::acos((double)(*ptr));
      return *this;
    }

    //! Compute the arc-sinus of each pixel value.
    CImg<typename cimg::superset<T,float>::type> get_asin() const {
      typedef typename cimg::superset<T,float>::type restype;
      return CImg<restype>(*this,false).asin();
    }

    //! In-place version of the previous function.
    CImg<T>& asin() {
      cimg_for(*this,ptr,T) (*ptr) = (T)std::asin((double)(*ptr));
      return *this;
    }

    //! Compute the arc-tangent of each pixel.
    CImg<typename cimg::superset<T,float>::type> get_atan() const {
      typedef typename cimg::superset<T,float>::type restype;
      return CImg<restype>(*this,false).atan();
    }

    //! In-place version of the previous function.
    CImg<T>& atan() {
      cimg_for(*this,ptr,T) (*ptr) = (T)std::atan((double)(*ptr));
      return *this;
    }

    //! Compute image with rounded pixel values.
    /**
       \param x Rounding precision.
       \param round_type Roundin type, can be 0 (nearest), 1 (forward), 2 (backward).
    **/
    CImg<T> get_round(const float x, const unsigned int round_type=0) const {
      return (+*this).round(x,round_type);
    }

    //! In-place version of the previous function.
    CImg<T>& round(const float x, const unsigned int round_type=0) {
      cimg_for(*this,ptr,T) (*ptr) = (T)cimg::round(*ptr,x,round_type);
      return *this;
    }

    //! Fill image with random values between specified range.
    CImg<T> get_rand(const T val_min, const T val_max) const {
      return (+*this).rand(val_min,val_max);
    }

    //! In-place version of the previous function.
    CImg<T>& rand(const T val_min, const T val_max) {
      const float delta = (float)val_max - (float)val_min;
      cimg_for(*this,ptr,T) *ptr = (T)(val_min + cimg::rand()*delta);
      return *this;
    }

    //@}
    //-----------------------------------
    //
    //! \name Usual Image Transformations
    //@{
    //-----------------------------------

    //! Fill an image by a value \p val.
    /**
       \param val = fill value
       \note All pixel values of the instance image will be initialized by \p val.
       \see operator=().
    **/
    CImg<T> get_fill(const T val) const {
      return CImg<T>(width,height,depth,dim).fill(val);
    }

    //! In-place version of the previous function.
    CImg<T>& fill(const T val) {
      if (!is_empty()) {
        if (val && sizeof(T)!=1) cimg_for(*this,ptr,T) *ptr = val;
        else std::memset(data,(int)val,size()*sizeof(T));
      }
      return *this;
    }

    //! Fill sequentially all pixel values with values \a val0 and \a val1 respectively.
    CImg<T> get_fill(const T val0, const T val1) const {
      return CImg<T>(width,height,depth,dim).fill(val0,val1);
    }

    //! In-place version of the previous function.
    CImg<T>& fill(const T val0, const T val1) {
      if (!is_empty()) {
        T *ptr, *ptr_end = end()-1;
        for (ptr=data; ptr<ptr_end; ) { *(ptr++) = val0; *(ptr++) = val1; }
        if (ptr!=ptr_end+1) *(ptr++) = val0;
      }
      return *this;
    }

    //! Fill sequentially all pixel values with values \a val0 and \a val1 and \a val2.
    CImg<T> get_fill(const T val0, const T val1, const T val2) const {
      return CImg<T>(width,height,depth,dim).fill(val0,val1,val2);
    }

    //! In-place version of the previous function.
    CImg<T>& fill(const T val0, const T val1, const T val2) {
      if (!is_empty()) {
        T *ptr, *ptr_end = end()-2;
        for (ptr=data; ptr<ptr_end; ) { *(ptr++) = val0; *(ptr++) = val1; *(ptr++) = val2; }
        ptr_end+=2;
        switch (ptr_end-ptr) {
        case 2: *(--ptr_end) = val1;
        case 1: *(--ptr_end) = val0;
        }
      }
      return *this;
    }

    //! Fill sequentially all pixel values with values \a val0 and \a val1 and \a val2 and \a val3.
    CImg<T> get_fill(const T val0, const T val1, const T val2, const T val3) const {
      return CImg<T>(width,height,depth,dim).fill(val0,val1,val2,val3);
    }

    //! In-place version of the previous function.
    CImg<T>& fill(const T val0, const T val1, const T val2, const T val3) {
      if (!is_empty()) {
        T *ptr, *ptr_end = end()-3;
        for (ptr=data; ptr<ptr_end; ) { *(ptr++) = val0; *(ptr++) = val1; *(ptr++) = val2; *(ptr++) = val3; }
        ptr_end+=3;
        switch (ptr_end-ptr) {
        case 3: *(--ptr_end) = val2;
        case 2: *(--ptr_end) = val1;
        case 1: *(--ptr_end) = val0;
        }
      }
      return *this;
    }

    //! Fill sequentially all pixel values with values \a val0 and \a val1 and \a val2 and \a val3 and \a val4.
    CImg<T> get_fill(const T val0, const T val1, const T val2, const T val3, const T val4) const {
      return CImg<T>(width,height,depth,dim).fill(val0,val1,val2,val3,val4);
    }

    //! In-place version of the previous function.
    CImg<T>& fill(const T val0, const T val1, const T val2, const T val3, const T val4) {
      if (!is_empty()) {
        T *ptr, *ptr_end = end()-4;
        for (ptr=data; ptr<ptr_end; ) { *(ptr++) = val0; *(ptr++) = val1; *(ptr++) = val2; *(ptr++) = val3; *(ptr++) = val4; }
        ptr_end+=4;
        switch (ptr_end-ptr) {
        case 4: *(--ptr_end) = val3;
        case 3: *(--ptr_end) = val2;
        case 2: *(--ptr_end) = val1;
        case 1: *(--ptr_end) = val0;
        }
      }
      return *this;
    }

    //! 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> get_fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5) const {
      return CImg<T>(width,height,depth,dim).fill(val0,val1,val2,val3,val4,val5);
    }

    //! In-place version of the previous function.
    CImg<T>& fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5) {
      if (!is_empty()) {
        T *ptr, *ptr_end = end()-5;
        for (ptr=data; ptr<ptr_end; ) {
          *(ptr++) = val0; *(ptr++) = val1; *(ptr++) = val2; *(ptr++) = val3; *(ptr++) = val4; *(ptr++) = val5;
        }
        ptr_end+=5;
        switch (ptr_end-ptr) {
        case 5: *(--ptr_end) = val4;
        case 4: *(--ptr_end) = val3;
        case 3: *(--ptr_end) = val2;
        case 2: *(--ptr_end) = val1;
        case 1: *(--ptr_end) = val0;
        }
      }
      return *this;
    }

    //! Fill sequentially pixel values.
    CImg<T> get_fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5, const T val6) const {
      return CImg<T>(width,height,depth,dim).fill(val0,val1,val2,val3,val4,val5,val6);
    }

    //! In-place version of the previous function.
    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()) {
        T *ptr, *ptr_end = end()-6;
        for (ptr=data; ptr<ptr_end; ) {
          *(ptr++) = val0; *(ptr++) = val1; *(ptr++) = val2; *(ptr++) = val3; *(ptr++) = val4; *(ptr++) = val5; *(ptr++) = val6;
        }
        ptr_end+=6;
        switch (ptr_end-ptr) {
        case 6: *(--ptr_end) = val5;
        case 5: *(--ptr_end) = val4;
        case 4: *(--ptr_end) = val3;
        case 3: *(--ptr_end) = val2;
        case 2: *(--ptr_end) = val1;
        case 1: *(--ptr_end) = val0;
        }
      }
      return *this;
    }

    //! Fill sequentially pixel values.
    CImg<T> get_fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5, const T val6,
                     const T val7) const {
      return CImg<T>(width,height,depth,dim).fill(val0,val1,val2,val3,val4,val5,val6,val7);
    }

    //! In-place version of the previous function.
    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()) {
        T *ptr, *ptr_end = end()-7;
        for (ptr=data; ptr<ptr_end; ) {
          *(ptr++) = val0; *(ptr++) = val1; *(ptr++) = val2; *(ptr++) = val3;
          *(ptr++) = val4; *(ptr++) = val5; *(ptr++) = val6; *(ptr++) = val7;
        }
        ptr_end+=7;
        switch (ptr_end-ptr) {
        case 7: *(--ptr_end) = val6;
        case 6: *(--ptr_end) = val5;
        case 5: *(--ptr_end) = val4;
        case 4: *(--ptr_end) = val3;
        case 3: *(--ptr_end) = val2;
        case 2: *(--ptr_end) = val1;
        case 1: *(--ptr_end) = val0;
        }
      }
      return *this;
    }

    //! Fill sequentially pixel values.
    CImg<T> get_fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5, const T val6,
                     const T val7, const T val8) const {
      return CImg<T>(width,height,depth,dim).fill(val0,val1,val2,val3,val4,val5,val6,val7,val8);
    }

    //! In-place version of the previous function.
    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()) {
        T *ptr, *ptr_end = end()-8;
        for (ptr=data; ptr<ptr_end; ) {
          *(ptr++) = val0; *(ptr++) = val1; *(ptr++) = val2;
          *(ptr++) = val3; *(ptr++) = val4; *(ptr++) = val5;
          *(ptr++) = val6; *(ptr++) = val7; *(ptr++) = val8;
        }
        ptr_end+=8;
        switch (ptr_end-ptr) {
        case 8: *(--ptr_end) = val7;
        case 7: *(--ptr_end) = val6;
        case 6: *(--ptr_end) = val5;
        case 5: *(--ptr_end) = val4;
        case 4: *(--ptr_end) = val3;
        case 3: *(--ptr_end) = val2;
        case 2: *(--ptr_end) = val1;
        case 1: *(--ptr_end) = val0;
        }
      }
      return *this;
    }

    //! Fill sequentially pixel values.
    CImg<T> get_fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5, const T val6,
                     const T val7, const T val8, const T val9) const {
      return CImg<T>(width,height,depth,dim).fill(val0,val1,val2,val3,val4,val5,val6,val7,val8,val9);
    }

    //! In-place version of the previous function.
    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()) {
        T *ptr, *ptr_end = end()-9;
        for (ptr=data; ptr<ptr_end; ) {
          *(ptr++) = val0; *(ptr++) = val1; *(ptr++) = val2; *(ptr++) = val3; *(ptr++) = val4;
          *(ptr++) = val5; *(ptr++) = val6; *(ptr++) = val7; *(ptr++) = val8; *(ptr++) = val9;
        }
        ptr_end+=9;
        switch (ptr_end-ptr) {
        case 9: *(--ptr_end) = val8;
        case 8: *(--ptr_end) = val7;
        case 7: *(--ptr_end) = val6;
        case 6: *(--ptr_end) = val5;
        case 5: *(--ptr_end) = val4;
        case 4: *(--ptr_end) = val3;
        case 3: *(--ptr_end) = val2;
        case 2: *(--ptr_end) = val1;
        case 1: *(--ptr_end) = val0;
        }
      }
      return *this;
    }

    //! Fill sequentially pixel values.
    CImg<T> get_fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5, const T val6,
                     const T val7, const T val8, const T val9, const T val10) const {
      return CImg<T>(width,height,depth,dim).fill(val0,val1,val2,val3,val4,val5,val6,val7,val8,val9,val10);
    }

    //! In-place version of the previous function.
    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()) {
        T *ptr, *ptr_end = end()-10;
        for (ptr=data; ptr<ptr_end; ) {
          *(ptr++) = val0; *(ptr++) = val1; *(ptr++) = val2; *(ptr++) = val3; *(ptr++) = val4;
          *(ptr++) = val5; *(ptr++) = val6; *(ptr++) = val7; *(ptr++) = val8; *(ptr++) = val9;
          *(ptr++) = val10;
        }
        ptr_end+=10;
        switch (ptr_end-ptr) {
        case 10: *(--ptr_end) = val9;
        case 9: *(--ptr_end) = val8;
        case 8: *(--ptr_end) = val7;
        case 7: *(--ptr_end) = val6;
        case 6: *(--ptr_end) = val5;
        case 5: *(--ptr_end) = val4;
        case 4: *(--ptr_end) = val3;
        case 3: *(--ptr_end) = val2;
        case 2: *(--ptr_end) = val1;
        case 1: *(--ptr_end) = val0;
        }
      }
      return *this;
    }

    //! Fill sequentially pixel values.
    CImg<T> get_fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5, const T val6,
                     const T val7, const T val8, const T val9, const T val10, const T val11) const {
      return CImg<T>(width,height,depth,dim).fill(val0,val1,val2,val3,val4,val5,val6,val7,val8,val9,val10,val11);
    }

    //! In-place version of the previous function.
    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()) {
        T *ptr, *ptr_end = end()-11;
        for (ptr=data; ptr<ptr_end; ) {
          *(ptr++) = val0; *(ptr++) = val1; *(ptr++) = val2; *(ptr++) = val3; *(ptr++) = val4; *(ptr++) = val5;
          *(ptr++) = val6; *(ptr++) = val7; *(ptr++) = val8; *(ptr++) = val9; *(ptr++) = val10; *(ptr++) = val11;
        }
        ptr_end+=11;
        switch (ptr_end-ptr) {
        case 11: *(--ptr_end) = val10;
        case 10: *(--ptr_end) = val9;
        case 9: *(--ptr_end) = val8;
        case 8: *(--ptr_end) = val7;
        case 7: *(--ptr_end) = val6;
        case 6: *(--ptr_end) = val5;
        case 5: *(--ptr_end) = val4;
        case 4: *(--ptr_end) = val3;
        case 3: *(--ptr_end) = val2;
        case 2: *(--ptr_end) = val1;
        case 1: *(--ptr_end) = val0;
        }
      }
      return *this;
    }

    //! Fill sequentially pixel values.
    CImg<T> get_fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5, const T val6,
                     const T val7, const T val8, const T val9, const T val10, const T val11, const T val12) const {
      return CImg<T>(width,height,depth,dim).fill(val0,val1,val2,val3,val4,val5,val6,val7,val8,val9,val10,val11,val12);
    }

    //! In-place version of the previous function.
    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()) {
        T *ptr, *ptr_end = end()-12;
        for (ptr=data; ptr<ptr_end; ) {
          *(ptr++) = val0; *(ptr++) = val1; *(ptr++) = val2; *(ptr++) = val3; *(ptr++) = val4; *(ptr++) = val5;
          *(ptr++) = val6; *(ptr++) = val7; *(ptr++) = val8; *(ptr++) = val9; *(ptr++) = val10; *(ptr++) = val11;
          *(ptr++) = val12;
        }
        ptr_end+=12;
        switch (ptr_end-ptr) {
        case 12: *(--ptr_end) = val11;
        case 11: *(--ptr_end) = val10;
        case 10: *(--ptr_end) = val9;
        case 9: *(--ptr_end) = val8;
        case 8: *(--ptr_end) = val7;
        case 7: *(--ptr_end) = val6;
        case 6: *(--ptr_end) = val5;
        case 5: *(--ptr_end) = val4;
        case 4: *(--ptr_end) = val3;
        case 3: *(--ptr_end) = val2;
        case 2: *(--ptr_end) = val1;
        case 1: *(--ptr_end) = val0;
        }
      }
      return *this;
    }

    //! Fill sequentially pixel values.
    CImg<T> get_fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5, const T val6,
                     const T val7, const T val8, const T val9, const T val10, const T val11, const T val12,
                     const T val13) const {
      return CImg<T>(width,height,depth,dim).fill(val0,val1,val2,val3,val4,val5,val6,val7,val8,val9,val10,val11,val12,val13);
    }

    //! In-place version of the previous function.
    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()) {
        T *ptr, *ptr_end = end()-13;
        for (ptr=data; ptr<ptr_end; ) {
          *(ptr++) = val0; *(ptr++) = val1; *(ptr++) = val2; *(ptr++) = val3; *(ptr++) = val4; *(ptr++) = val5;
          *(ptr++) = val6; *(ptr++) = val7; *(ptr++) = val8; *(ptr++) = val9; *(ptr++) = val10; *(ptr++) = val11;
          *(ptr++) = val12; *(ptr++) = val13;
        }
        ptr_end+=13;
        switch (ptr_end-ptr) {
        case 13: *(--ptr_end) = val12;
        case 12: *(--ptr_end) = val11;
        case 11: *(--ptr_end) = val10;
        case 10: *(--ptr_end) = val9;
        case 9: *(--ptr_end) = val8;
        case 8: *(--ptr_end) = val7;
        case 7: *(--ptr_end) = val6;
        case 6: *(--ptr_end) = val5;
        case 5: *(--ptr_end) = val4;
        case 4: *(--ptr_end) = val3;
        case 3: *(--ptr_end) = val2;
        case 2: *(--ptr_end) = val1;
        case 1: *(--ptr_end) = val0;
        }
      }
      return *this;
    }

    //! Fill sequentially pixel values.
    CImg<T> get_fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5, const T val6,
                     const T val7, const T val8, const T val9, const T val10, const T val11, const T val12,
                     const T val13, const T val14) const {
      return CImg<T>(width,height,depth,dim).fill(val0,val1,val2,val3,val4,val5,val6,val7,val8,val9,val10,val11,val12,val13,val14);
    }

    //! In-place version of the previous function.
    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()) {
        T *ptr, *ptr_end = end()-14;
        for (ptr=data; ptr<ptr_end; ) {
          *(ptr++) = val0; *(ptr++) = val1; *(ptr++) = val2; *(ptr++) = val3; *(ptr++) = val4; *(ptr++) = val5;
          *(ptr++) = val6; *(ptr++) = val7; *(ptr++) = val8; *(ptr++) = val9; *(ptr++) = val10; *(ptr++) = val11;
          *(ptr++) = val12; *(ptr++) = val13; *(ptr++) = val14;
        }
        ptr_end+=14;
        switch (ptr_end-ptr) {
        case 14: *(--ptr_end) = val13;
        case 13: *(--ptr_end) = val12;
        case 12: *(--ptr_end) = val11;
        case 11: *(--ptr_end) = val10;
        case 10: *(--ptr_end) = val9;
        case 9: *(--ptr_end) = val8;
        case 8: *(--ptr_end) = val7;
        case 7: *(--ptr_end) = val6;
        case 6: *(--ptr_end) = val5;
        case 5: *(--ptr_end) = val4;
        case 4: *(--ptr_end) = val3;
        case 3: *(--ptr_end) = val2;
        case 2: *(--ptr_end) = val1;
        case 1: *(--ptr_end) = val0;
        }
      }
      return *this;
    }

    //! Fill sequentially pixel values.
    CImg<T> get_fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5, const T val6,
                     const T val7, const T val8, const T val9, const T val10, const T val11, const T val12,
                     const T val13, const T val14, const T val15) const {
      return CImg<T>(width,height,depth,dim).fill(val0,val1,val2,val3,val4,val5,val6,val7,val8,val9,val10,val11,val12,val13,val14,val15);
    }

    //! In-place version of the previous function.
    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()) {
        T *ptr, *ptr_end = end()-15;
        for (ptr=data; ptr<ptr_end; ) {
          *(ptr++) = val0; *(ptr++) = val1; *(ptr++) = val2; *(ptr++) = val3; *(ptr++) = val4; *(ptr++) = val5;
          *(ptr++) = val6; *(ptr++) = val7; *(ptr++) = val8; *(ptr++) = val9; *(ptr++) = val10; *(ptr++) = val11;
          *(ptr++) = val12; *(ptr++) = val13; *(ptr++) = val14; *(ptr++) = val15;
        }
        ptr_end+=15;
        switch (ptr_end-ptr) {
        case 15: *(--ptr_end) = val14;
        case 14: *(--ptr_end) = val13;
        case 13: *(--ptr_end) = val12;
        case 12: *(--ptr_end) = val11;
        case 11: *(--ptr_end) = val10;
        case 10: *(--ptr_end) = val9;
        case 9: *(--ptr_end) = val8;
        case 8: *(--ptr_end) = val7;
        case 7: *(--ptr_end) = val6;
        case 6: *(--ptr_end) = val5;
        case 5: *(--ptr_end) = val4;
        case 4: *(--ptr_end) = val3;
        case 3: *(--ptr_end) = val2;
        case 2: *(--ptr_end) = val1;
        case 1: *(--ptr_end) = val0;
        }
      }
      return *this;
    }

    //! Fill sequentially pixel values.
    template<int N> CImg<T> get_fill(const int val0, ...) const {
      CImg<T> res(*this,false);
      va_list ap;
      va_start(ap,val0);
      res._fill<N,int>(val0,ap);
      va_end(ap);
      return res;
    }

    //! In-place version of the previous function.
    template<int N> CImg<T>& fill(const int val0, ...) {
      va_list ap;
      va_start(ap,val0);
      _fill<N,int>(val0,ap);
      va_end(ap);
      return *this;
    }

    //! Fill sequentially pixel values.
    template<int N> CImg<T> get_fill(const double val0, ...) const {
      CImg<T> res(*this,false);
      va_list ap;
      va_start(ap,val0);
      res._fill<N,double>(val0,ap);
      va_end(ap);
      return res;
    }

    //! In-place version of the previous function.
    template<int N> CImg<T>& fill(const double val0, ...) {
      va_list ap;
      va_start(ap,val0);
      _fill<N,double>(val0,ap);
      va_end(ap);
      return *this;
    }

    template<int N, typename t> CImg<T>& _fill(const t val0, va_list &ap) {
      if (N>0 && !is_empty()) {
	CImg<T> vals(N);
	T *ptrs = vals.data;
	*(ptrs++) = (T)val0;
	for (int i=1; i<N; ++i) *(ptrs++) = (T)va_arg(ap,t);
	ptrs-=N;
	T *ptr, *ptr_end = end()+1-N;
	for (ptr=data; ptr<ptr_end; ) { ptrs = vals.data; for (int i=0; i<N; ++i) *(ptr++) = *(ptrs++); }
	ptr_end+=N-1;
	ptrs = vals.data;
	for (int i = ptr_end-ptr; i>0; --i) *(ptr++) = *(ptrs++);
      }
      return *this;
    }

    //! Fill image values according to the values found in the specified string.
    CImg<T>& fill(const char *const values, const bool repeat_pattern) {
      T *ptrd = data, *ptr_end = data + size();
      const char *nvalues = values;
      char tmp[64] = { 0 },  cval[64] = { 0 };
      double val = 0;
      unsigned int nb = 0;
      while (std::sscanf(nvalues,"%63[ \n\t0-9e.+-]%63[^0-9.+-]",cval,tmp)>0
	     && std::sscanf(cval,"%lf",&val)>0) {
        nvalues += cimg::strlen(tmp) + cimg::strlen(cval);
        *(ptrd++) = (T)val;
	++nb;
      }
      if (nb && repeat_pattern) for (T *ptrs = data; ptrd<ptr_end; ++ptrs) *(ptrd++) = *ptrs;
      return *this;
    }

    //! Fill image values along the V-axis at the specified pixel position (x,y,z) (int version)
    CImg<T>& fillV(const unsigned int x, const unsigned int y, const unsigned int z, const int a0, ...) {

#define _cimg_fillv(x,y,z,a0,t) if (x<width && y<height && z<depth) { \
    va_list ap; va_start(ap,a0); \
    const unsigned int whz = width*height*depth; \
    T *ptrd = ptr(x,y,z,0); *ptrd = (T)a0; \
    for (unsigned int k=1; k<dim; ++k) { ptrd+=whz; *ptrd = (T)va_arg(ap,t); } \
    va_end(ap); }
      _cimg_fillv(x,y,z,a0,int);
      return *this;
    }

    //! Fill image values along the V-axis at the specified pixel position (x,y,z) (double version)
    CImg<T>& fillV(const unsigned int x, const unsigned int y, const unsigned int z, const double a0, ...) {
      _cimg_fillv(x,y,z,a0,double);
      return *this;
    }

    //! Fill image values along the ZV-axes at the specified pixel position (x,y) (int version)
    CImg<T>& fillZV(const unsigned int x, const unsigned int y, const int a0, ...) {

#define _cimg_fillzv(x,y,a0,t) if (x<width && y<height) { \
    va_list ap; va_start(ap,a0); \
    const unsigned int wh = width*height, zd = depth*dim; \
    T *ptrd = ptr(x,y,0,0); *ptrd = (T)a0; \
    for (unsigned int k=1; k<zd; ++k) { ptrd+=wh; *ptrd = (T)va_arg(ap,t); } \
    va_end(ap); }
      _cimg_fillzv(x,y,a0,int);
      return *this;
    }

    //! Fill image values along the ZV-axes at the specified pixel position (x,y) (double version)
    CImg<T>& fillZV(const unsigned int x, const unsigned int y, const double a0, ...) {
      _cimg_fillzv(x,y,a0,double);
      return *this;
    }

    //! Fill image values along the YZV-axes at the specified pixel position x (int version)
    CImg<T>& fillYZV(const unsigned int x, const int a0, ...) {
#define _cimg_fillyzv(x,a0,t) if (x<width) { \
    va_list ap; va_start(ap,a0); \
    const unsigned int hzd = height*depth*dim; \
    T *ptrd = ptr(x,0,0,0); *ptrd = (T)a0; \
    for (unsigned int k=1; k<hzd; ++k) { ptrd+=width; *ptrd = (T)va_arg(ap,t); } \
    va_end(ap); \
}
      _cimg_fillyzv(x,a0,int);
      return *this;
    }

    //! Fill image values along the YZV-axes at the specified pixel position x (double version)
    CImg<T>& fillYZV(const unsigned int x, const double a0, ...) {
      _cimg_fillyzv(x,a0,double);
      return *this;
    }

    //! Linear normalization of the pixel values between \a a and \a b.
    /**
       \param a = minimum pixel value after normalization.
       \param b = maximum pixel value after normalization.
    **/
    CImg<T> get_normalize(const T a, const T b) const {
      return (+*this).normalize(a,b);
    }

    //! In-place version of the previous function.
    CImg<T>& normalize(const T a, const T b) {
      if (!is_empty()) {
        typedef typename cimg::superset<T,float>::type ftype;
        T m, M = maxmin(m);
        const ftype fm = (ftype)m, fM = (ftype)M;
        if (m==M) return fill(0);
        if (m!=a || M!=b) cimg_for(*this,ptr,T) *ptr = (T)((*ptr-fm)/(fM-fm)*(b-a)+a);
      }
      return *this;
    }

    //! Cut pixel values between \a a and \a b.
    /**
       \param a = minimum pixel value after cut.
       \param b = maximum pixel value after cut.
    **/
    CImg<T> get_cut(const T a, const T b) const {
      return (+*this).cut(a,b);
    }

    //! In-place version of the previous function.
    CImg<T>& cut(const T a, const T b) {
      if (!is_empty())
        cimg_for(*this,ptr,T) *ptr = (*ptr<a)?a:((*ptr>b)?b:*ptr);
      return *this;
    }

    //! Quantize pixel values into \n levels.
    /**
       \param n = number of quantification levels
       \param keep_range = keep same value range.
    **/
    CImg<T> get_quantize(const unsigned int n=256, const bool keep_range=true) const {
      return (+*this).quantize(n,keep_range);
    }

    //! In-place version of the previous function.
    CImg<T>& quantize(const unsigned int n=256, const bool keep_range=true) {
      if (!is_empty()) {
        if (!n) throw CImgArgumentException("CImg<%s>::quantize() : Cannot quantize image to 0 values.",
                                            pixel_type());
        typedef typename cimg::superset<T,float>::type ftype;
        ftype m, M = (ftype)maxmin(m), range = M - m;
        if (range>0) {
          if (keep_range) cimg_for(*this,ptr,T) {
            const unsigned int val = (unsigned int)((*ptr-m)*n/range);
            *ptr = (T)(m + cimg::min(val,n-1)*range/n);
          } else cimg_for(*this,ptr,T) {
            const unsigned int val = (unsigned int)((*ptr-m)*n/range);
            *ptr = (T)cimg::min(val,n-1);
          }
        }
      }
      return *this;
    }

    //! Threshold the image.
    /**
       \param thres = threshold
    **/
    CImg<T> get_threshold(const T thres) const {
      return (+*this).threshold(thres);
    }

    //! In-place version of the previous function.
    CImg<T>& threshold(const T thres) {
      if (!is_empty()) cimg_for(*this,ptr,T) *ptr = *ptr<=thres?(T)0:(T)1;
      return *this;
    }

    //! Return a rotated image.
    /**
       \param angle = rotation angle (in degrees).
       \param cond = rotation type. can be :
       - 0 = zero-value at borders
       - 1 = repeat image at borders
       - 2 = zero-value at borders and linear interpolation
       \note Returned image will probably have a different size than the instance image *this.
    **/
    CImg<T> get_rotate(const float angle, const unsigned int cond=3) const {
      if (is_empty()) return CImg<T>();
      CImg<T> dest;
      const float nangle = cimg::mod(angle,360.0f), rad = (float)((nangle*cimg::valuePI)/180.0),
        ca=(float)std::cos(rad), sa=(float)std::sin(rad);
      if (cond!=1 && cimg::mod(nangle,90.0f)==0) { // optimized version for orthogonal angles
        const int wm1 = dimx()-1, hm1 = dimy()-1;
        const int iangle = (int)nangle/90;
        switch (iangle) {
        case 1: {
          dest.assign(height,width,depth,dim);
          cimg_forXYZV(dest,x,y,z,v) dest(x,y,z,v) = (*this)(y,hm1-x,z,v);
        } break;
        case 2: {
          dest.assign(width,height,depth,dim);
          cimg_forXYZV(dest,x,y,z,v) dest(x,y,z,v) = (*this)(wm1-x,hm1-y,z,v);
        } break;
        case 3: {
          dest.assign(height,width,depth,dim);
          cimg_forXYZV(dest,x,y,z,v) dest(x,y,z,v) = (*this)(wm1-y,x,z,v);
        } break;
        default:
          return *this;
        }
      } else { // generic version
        const float
          ux  = (float)(cimg::abs(width*ca)),  uy  = (float)(cimg::abs(width*sa)),
          vx  = (float)(cimg::abs(height*sa)), vy  = (float)(cimg::abs(height*ca)),
          w2  = 0.5f*width,           h2  = 0.5f*height,
          dw2 = 0.5f*(ux+vx),         dh2 = 0.5f*(uy+vy);
        dest.assign((int)(ux+vx), (int)(uy+vy),depth,dim);
        switch (cond) {
        case 0: {
          cimg_forXY(dest,x,y)
            cimg_forZV(*this,z,v)
            dest(x,y,z,v) = pix2d((int)(w2 + (x-dw2)*ca + (y-dh2)*sa),(int)(h2 - (x-dw2)*sa + (y-dh2)*ca),z,v,0);
        } break;
        case 1: {
          cimg_forXY(dest,x,y)
            cimg_forZV(*this,z,v)
            dest(x,y,z,v) = (*this)(cimg::mod((int)(w2 + (x-dw2)*ca + (y-dh2)*sa),dimx()),
                                    cimg::mod((int)(h2 - (x-dw2)*sa + (y-dh2)*ca),dimy()),z,v);
        } break;
        case 2: {
          cimg_forXY(dest,x,y) {
            const float X = w2 + (x-dw2)*ca + (y-dh2)*sa, Y = h2 - (x-dw2)*sa + (y-dh2)*ca;
            cimg_forZV(*this,z,v) dest(x,y,z,v) = (T)linear_pix2d(X,Y,z,v,0);
          }
        } break;
        default: {
          cimg_forXY(dest,x,y) {
            const float X = w2 + (x-dw2)*ca + (y-dh2)*sa, Y = h2 - (x-dw2)*sa + (y-dh2)*ca;
            cimg_forZV(*this,z,v) dest(x,y,z,v) = (T)cubic_pix2d(X,Y,z,v,0);
          }
        } break;
        }
      }
      return dest;
    }

    //! In-place version of the previous function.
    CImg<T>& rotate(const float angle, const unsigned int cond=3) {
      return get_rotate(angle,cond).transfer_to(*this);
    }

    //! Return a rotated image around the 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
       \see rotate()
    **/
    CImg<T> get_rotate(const float angle, const float cx, const float cy, const float zoom=1, const unsigned int cond=3) const {
      if (is_empty()) return CImg<T>();
      CImg<T> dest(width,height,depth,dim);
      const float nangle = cimg::mod(angle,360.0f), rad = (float)((nangle*cimg::valuePI)/180.0),
        ca=(float)std::cos(rad)/zoom, sa=(float)std::sin(rad)/zoom;
      if (cond!=1 && zoom==1 && cimg::mod(nangle,90.0f)==0) { // optimized version for orthogonal angles
        const int iangle = (int)nangle/90;
        switch (iangle) {
        case 1: {
          dest.fill(0);
          const unsigned int
            xmin = cimg::max(0,(dimx()-dimy())/2), xmax = cimg::min(width,xmin+height),
            ymin = cimg::max(0,(dimy()-dimx())/2), ymax = cimg::min(height,ymin+width),
            xoff = xmin + cimg::min(0,(dimx()-dimy())/2),
            yoff = ymin + cimg::min(0,(dimy()-dimx())/2);
          cimg_forZV(dest,z,v) for (unsigned int y=ymin; y<ymax; ++y) for (unsigned int x=xmin; x<xmax; ++x)
            dest(x,y,z,v) = (*this)(y-yoff,height-1-x+xoff,z,v);
        } break;
        case 2: {
          cimg_forXYZV(dest,x,y,z,v) dest(x,y,z,v) = (*this)(width-1-x,height-1-y,z,v);
        } break;
        case 3: {
          dest.fill(0);
          const unsigned int
            xmin = cimg::max(0,(dimx()-dimy())/2), xmax = cimg::min(width,xmin+height),
            ymin = cimg::max(0,(dimy()-dimx())/2), ymax = cimg::min(height,ymin+width),
            xoff = xmin + cimg::min(0,(dimx()-dimy())/2),
            yoff = ymin + cimg::min(0,(dimy()-dimx())/2);
          cimg_forZV(dest,z,v) for (unsigned int y=ymin; y<ymax; ++y) for (unsigned int x=xmin; x<xmax; ++x)
            dest(x,y,z,v) = (*this)(width-1-y+yoff,x-xoff,z,v);
        } break;
        default:
          return *this;
        }
      } else
        switch (cond) { // generic version
        case 0: {
          cimg_forXY(dest,x,y)
            cimg_forZV(*this,z,v)
            dest(x,y,z,v) = pix2d((int)(cx + (x-cx)*ca + (y-cy)*sa),(int)(cy - (x-cx)*sa + (y-cy)*ca),z,v,0);
        } break;
        case 1: {
          cimg_forXY(dest,x,y)
            cimg_forZV(*this,z,v)
            dest(x,y,z,v) = (*this)(cimg::mod((int)(cx + (x-cx)*ca + (y-cy)*sa),dimx()),
                                    cimg::mod((int)(cy - (x-cx)*sa + (y-cy)*ca),dimy()),z,v);
        } break;
        case 2: {
          cimg_forXY(dest,x,y) {
            const float X = cx + (x-cx)*ca + (y-cy)*sa, Y = cy - (x-cx)*sa + (y-cy)*ca;
            cimg_forZV(*this,z,v) dest(x,y,z,v) = (T)linear_pix2d(X,Y,z,v,0);
          }
        } break;
        default: {
          cimg_forXY(dest,x,y) {
            const float X = cx + (x-cx)*ca + (y-cy)*sa, Y = cy - (x-cx)*sa + (y-cy)*ca;
            cimg_forZV(*this,z,v) dest(x,y,z,v) = (T)cubic_pix2d(X,Y,z,v,0);
          }
        } break;
        }
      return dest;
    }

    //! In-place version of the previous function.
    CImg<T>& rotate(const float angle, const float cx, const float cy, const float zoom=1, const unsigned int cond=3) {
      return get_rotate(angle,cx,cy,zoom,cond).transfer_to(*this);
    }

    //! Return a resized image.
    /**
       \param pdx Number of columns (new size along the X-axis).
       \param pdy Number of rows (new size along the Y-axis).
       \param pdz Number of slices (new size along the Z-axis).
       \param pdv Number of vector-channels (new size along the V-axis).
       \param interp Method of interpolation :
       - -1 = no interpolation : raw memory resizing.
       - 0 = no interpolation : additional space is filled according to \p border_condition.
       - 1 = bloc interpolation (nearest point).
       - 2 = moving average interpolation.
       - 3 = linear interpolation.
       - 4 = grid interpolation.
       - 5 = bi-cubic interpolation.
       \param border_condition Border condition type.
       \param center Set centering type (only if \p interp=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> get_resize(const int pdx=-100, const int pdy=-100, const int pdz=-100, const int pdv=-100,
                       const int interp=1, const int border_condition=-1, const bool center=false) const {
      if (!pdx || !pdy || !pdz || !pdv) return CImg<T>();
      const unsigned int
        tdx = pdx<0?-pdx*width/100:pdx,
        tdy = pdy<0?-pdy*height/100:pdy,
        tdz = pdz<0?-pdz*depth/100:pdz,
        tdv = pdv<0?-pdv*dim/100:pdv,
        dx = tdx?tdx:1,
        dy = tdy?tdy:1,
        dz = tdz?tdz:1,
        dv = tdv?tdv:1;
      if (width==dx && height==dy && depth==dz && dim==dv) return +*this;
      if (is_empty()) return CImg<T>(dx,dy,dz,dv,0);

      CImg<T> res;

      switch (interp) {
      case -1: // Raw resizing
        std::memcpy(res.assign(dx,dy,dz,dv,0).data,data,sizeof(T)*cimg::min(size(),(long unsigned int)dx*dy*dz*dv));
        break;

      case 0:  { // No interpolation
        const unsigned int bx = width-1, by = height-1, bz = depth-1, bv = dim-1;
        res.assign(dx,dy,dz,dv);
        switch (border_condition) {
        case 1: {
          if (center) {
            const int
              x0 = (res.dimx()-dimx())/2,
              y0 = (res.dimy()-dimy())/2,
              z0 = (res.dimz()-dimz())/2,
              v0 = (res.dimv()-dimv())/2,
              x1 = x0 + (int)bx,
              y1 = y0 + (int)by,
              z1 = z0 + (int)bz,
              v1 = v0 + (int)bv;
            res.draw_image(*this,x0,y0,z0,v0);
            cimg_for_outXYZV(res,x0,y0,z0,v0,x1,y1,z1,v1,x,y,z,v) res(x,y,z,v) = pix4d(x-x0,y-y0,z-z0,v-v0);
          } else {
            res.draw_image(*this,0,0,0,0);
            cimg_for_outXYZV(res,0,0,0,0,bx,by,bz,bv,x,y,z,v) res(x,y,z,v) = pix4d(x,y,z,v);
          }
          } break;
        case 2 : {
          const int
            x0 = (res.dimx()-dimx())/2,
            y0 = (res.dimy()-dimy())/2,
            z0 = (res.dimz()-dimz())/2,
            v0 = (res.dimv()-dimv())/2,
            nx0 = x0>0?x0-(1+x0/width)*width:x0,
            ny0 = y0>0?y0-(1+y0/height)*height:y0,
            nz0 = z0>0?z0-(1+z0/depth)*depth:z0,
            nv0 = v0>0?v0-(1+v0/dim)*dim:v0;
          for (int k=nv0; k<(int)dv; k+=dimv())
            for (int z=nz0; z<(int)dz; z+=dimz())
              for (int y=ny0; y<(int)dy; y+=dimy())
                for (int x=nx0; x<(int)dx; x+=dimx()) res.draw_image(*this,x,y,z,k);
          } break;
        default: {
          res.fill(0);
          if (center) res.draw_image(*this,(res.dimx()-dimx())/2,(res.dimy()-dimy())/2,(res.dimz()-dimz())/2,(res.dimv()-dimv())/2);
          else res.draw_image(*this,0,0,0,0);
        } break;
        }
      } break;

      case 1: { // Nearest-neighbor interpolation
        res.assign(dx,dy,dz,dv);
        unsigned int
          *const offx = new unsigned int[dx],
          *const offy = new unsigned int[dy+1],
          *const offz = new unsigned int[dz+1],
          *const offv = new unsigned int[dv+1],
          *poffx, *poffy, *poffz, *poffv,
          curr, old;
        const unsigned int wh = width*height, whd = width*height*depth, rwh = dx*dy, rwhd = dx*dy*dz;
        poffx = offx; curr = 0; { cimg_forX(res,x) { old=curr; curr=(x+1)*width/dx; *(poffx++) = (unsigned int)curr-(unsigned int)old; }}
        poffy = offy; curr = 0; { cimg_forY(res,y) { old=curr; curr=(y+1)*height/dy; *(poffy++) = width*((unsigned int)curr-(unsigned int)old); }} *poffy=0;
        poffz = offz; curr = 0; { cimg_forZ(res,z) { old=curr; curr=(z+1)*depth/dz; *(poffz++) = wh*((unsigned int)curr-(unsigned int)old); }} *poffz=0;
        poffv = offv; curr = 0; { cimg_forV(res,k) { old=curr; curr=(k+1)*dim/dv; *(poffv++) = whd*((unsigned int)curr-(unsigned int)old); }} *poffv=0;
        T *ptrd = res.data;
        const T* ptrv = data;
        poffv = offv;
        for (unsigned int k=0; k<dv; ) {
          const T *ptrz = ptrv;
          poffz = offz;
          for (unsigned int z=0; z<dz; ) {
            const T *ptry = ptrz;
            poffy = offy;
            for (unsigned int y=0; y<dy; ) {
              const T *ptrx = ptry;
              poffx = offx;
              cimg_forX(res,x) { *(ptrd++) = *ptrx; ptrx+=*(poffx++); }
              ++y;
              unsigned int dy = *(poffy++);
              for (;!dy && y<dy; std::memcpy(ptrd, ptrd-dx, sizeof(T)*dx), ++y, ptrd+=dx, dy=*(poffy++)) {}
              ptry+=dy;
            }
            ++z;
            unsigned int dz = *(poffz++);
            for (;!dz && z<dz; std::memcpy(ptrd, ptrd-rwh, sizeof(T)*rwh), ++z, ptrd+=rwh, dz=*(poffz++)) {}
            ptrz+=dz;
          }
          ++k;
          unsigned int dv = *(poffv++);
          for (;!dv && k<dv; std::memcpy(ptrd, ptrd-rwhd, sizeof(T)*rwhd), ++k, ptrd+=rwhd, dv=*(poffv++)) {}
          ptrv+=dv;
        }
        delete[] offx; delete[] offy; delete[] offz; delete[] offv;
      } break;

      case 2: { // Moving average
        typedef typename cimg::superset<T,float>::type ftype;
        bool instance_first = true;
        if (dx!=width) {
          CImg<ftype> tmp(dx,height,depth,dim,0);
          for (unsigned int a=width*dx, b=width, c=dx, s=0, t=0; a; ) {
            const unsigned int d = cimg::min(b,c);
            a-=d; b-=d; c-=d;
            cimg_forYZV(tmp,y,z,v) tmp(t,y,z,v)+=(ftype)(*this)(s,y,z,v)*d;
            if (!b) { cimg_forYZV(tmp,y,z,v) tmp(t,y,z,v)/=width; ++t; b = width; }
            if (!c) { ++s; c = dx; }
          }
          tmp.transfer_to(res);
          instance_first = false;
        }
        if (dy!=height) {
          CImg<ftype> tmp(dx,dy,depth,dim,0);
          for (unsigned int a=height*dy, b=height, c=dy, s=0, t=0; a; ) {
            const unsigned int d = cimg::min(b,c);
            a-=d; b-=d; c-=d;
            if (instance_first) cimg_forXZV(tmp,x,z,v) tmp(x,t,z,v)+=(ftype)(*this)(x,s,z,v)*d;
            else cimg_forXZV(tmp,x,z,v) tmp(x,t,z,v)+=(ftype)res(x,s,z,v)*d;
            if (!b) { cimg_forXZV(tmp,x,z,v) tmp(x,t,z,v)/=height; ++t; b = height; }
            if (!c) { ++s; c = dy; }
          }
          tmp.transfer_to(res);
          instance_first = false;
        }
        if (dz!=depth) {
          CImg<ftype> tmp(dx,dy,dz,dim,0);
          for (unsigned int a=depth*dz, b=depth, c=dz, s=0, t=0; a; ) {
            const unsigned int d = cimg::min(b,c);
            a-=d; b-=d; c-=d;
            if (instance_first) cimg_forXYV(tmp,x,y,v) tmp(x,y,t,v)+=(ftype)(*this)(x,y,s,v)*d;
            else cimg_forXYV(tmp,x,y,v) tmp(x,y,t,v)+=(ftype)res(x,y,s,v)*d;
            if (!b) { cimg_forXYV(tmp,x,y,v) tmp(x,y,t,v)/=depth; ++t; b = depth; }
            if (!c) { ++s; c = dz; }
          }
          tmp.transfer_to(res);
          instance_first = false;
        }
        if (dv!=dim) {
          CImg<ftype> tmp(dx,dy,dz,dv,0);
          for (unsigned int a=dim*dv, b=dim, c=dv, s=0, t=0; a; ) {
            const unsigned int d = cimg::min(b,c);
            a-=d; b-=d; c-=d;
            if (instance_first) cimg_forXYZ(tmp,x,y,z) tmp(x,y,z,t)+=(ftype)(*this)(x,y,z,s)*d;
            else cimg_forXYZ(tmp,x,y,z) tmp(x,y,z,t)+=(ftype)res(x,y,z,s)*d;
            if (!b) { cimg_forXYZ(tmp,x,y,z) tmp(x,y,z,t)/=dim; ++t; b = dim; }
            if (!c) { ++s; c = dv; }
          }
          tmp.transfer_to(res);
          instance_first = false;
        }
      } break;

      case 3: { // Linear interpolation
        const unsigned int dimmax = cimg::max(dx,dy,dz,dv);
        const float
          sx = (border_condition<0 && dx>width )?(dx>1?(width-1.0f)/(dx-1) :0):(float)width/dx,
          sy = (border_condition<0 && dy>height)?(dy>1?(height-1.0f)/(dy-1):0):(float)height/dy,
          sz = (border_condition<0 && dz>depth )?(dz>1?(depth-1.0f)/(dz-1) :0):(float)depth/dz,
          sv = (border_condition<0 && dv>dim   )?(dv>1?(dim-1.0f)/(dv-1)   :0):(float)dim/dv;

        unsigned int *const off = new unsigned int[dimmax], *poff;
        float *const foff = new float[dimmax], *pfoff, old, curr;
        CImg<T> resx, resy, resz, resv;
        T *ptrd;

        if (dx!=width) {
          if (width==1) resx = get_resize(dx,height,depth,dim,1,0);
          else {
            resx.assign(dx,height,depth,dim);
            curr = old = 0; poff = off; pfoff = foff;
            cimg_forX(resx,x) { *(pfoff++) = curr-(unsigned int)curr; old = curr; curr+=sx; *(poff++) = (unsigned int)curr-(unsigned int)old; }
            ptrd = resx.data;
            const T *ptrs0 = data;
            cimg_forYZV(resx,y,z,k) {
              poff = off; pfoff = foff;
              const T *ptrs = ptrs0, *const ptrsmax = ptrs0 + (width-1);
              cimg_forX(resx,x) {
                const float alpha = *(pfoff++);
                const T val1 = *ptrs, val2 = ptrs<ptrsmax?*(ptrs+1):(border_condition?val1:0);
                *(ptrd++) = (T)((1-alpha)*val1 + alpha*val2);
                ptrs+=*(poff++);
              }
              ptrs0+=width;
            }
          }
        } else resx.assign(*this,true);

        if (dy!=height) {
          if (height==1) resy = resx.get_resize(dx,dy,depth,dim,1,0);
          else {
            resy.assign(dx,dy,depth,dim);
            curr = old = 0; poff = off; pfoff = foff;
            cimg_forY(resy,y) { *(pfoff++) = curr-(unsigned int)curr; old = curr; curr+=sy; *(poff++) = dx*((unsigned int)curr-(unsigned int)old); }
            cimg_forXZV(resy,x,z,k) {
              ptrd = resy.ptr(x,0,z,k);
              const T *ptrs = resx.ptr(x,0,z,k), *const ptrsmax = ptrs + (height-1)*dx;
              poff = off; pfoff = foff;
              cimg_forY(resy,y) {
                const float alpha = *(pfoff++);
                const T val1 = *ptrs, val2 = ptrs<ptrsmax?*(ptrs+dx):(border_condition?val1:0);
                *ptrd = (T)((1-alpha)*val1 + alpha*val2);
                ptrd+=dx;
                ptrs+=*(poff++);
              }
            }
          }
          resx.assign();
        } else resy.assign(resx,true);

        if (dz!=depth) {
          if (depth==1) resz = resy.get_resize(dx,dy,dz,dim,1,0);
          else {
            const unsigned int wh = dx*dy;
            resz.assign(dx,dy,dz,dim);
            curr = old = 0; poff = off; pfoff = foff;
            cimg_forZ(resz,z) { *(pfoff++) = curr-(unsigned int)curr; old = curr; curr+=sz; *(poff++) = wh*((unsigned int)curr-(unsigned int)old); }
            cimg_forXYV(resz,x,y,k) {
              ptrd = resz.ptr(x,y,0,k);
              const T *ptrs = resy.ptr(x,y,0,k), *const ptrsmax = ptrs + (depth-1)*wh;
              poff = off; pfoff = foff;
              cimg_forZ(resz,z) {
                const float alpha = *(pfoff++);
                const T val1 = *ptrs, val2 = ptrs<ptrsmax?*(ptrs+wh):(border_condition?val1:0);
                *ptrd = (T)((1-alpha)*val1 + alpha*val2);
                ptrd+=wh;
                ptrs+=*(poff++);
              }
            }
          }
          resy.assign();
        } else resz.assign(resy,true);

        if (dv!=dim) {
          if (dim==1) resv = resz.get_resize(dx,dy,dz,dv,1,0);
          else {
            const unsigned int whd = dx*dy*dz;
            resv.assign(dx,dy,dz,dv);
            curr = old = 0; poff = off; pfoff = foff;
            cimg_forV(resv,k) { *(pfoff++) = curr-(unsigned int)curr; old = curr; curr+=sv; *(poff++) = whd*((unsigned int)curr-(unsigned int)old); }
            cimg_forXYZ(resv,x,y,z) {
              ptrd = resv.ptr(x,y,z,0);
              const T *ptrs = resz.ptr(x,y,z,0), *const ptrsmax = ptrs + (dim-1)*whd;
              poff = off; pfoff = foff;
              cimg_forV(resv,k) {
                const float alpha = *(pfoff++);
                const T val1 = *ptrs, val2 = ptrs<ptrsmax?*(ptrs+whd):(border_condition?val1:0);
                *ptrd = (T)((1-alpha)*val1 + alpha*val2);
                ptrd+=whd;
                ptrs+=*(poff++);
              }
            }
          }
          resz.assign();
        } else resv.assign(resz,true);

        delete[] off; delete[] foff;
        return resv.is_shared?(resz.is_shared?(resy.is_shared?(resx.is_shared?(+(*this)):resx):resy):resz):resv;
      } break;

      case 4: { // Grid filling
        res.assign(dx,dy,dz,dv,0);
        cimg_forXYZV(*this,x,y,z,k) res(x*dx/width,y*dy/height,z*dz/depth,k*dv/dim) = (*this)(x,y,z,k);
      } break;

      case 5: { // Cubic interpolation
        typedef typename cimg::superset<T,float>::type ftype;
        const float
          sx = (border_condition<0 && dx>width )?(dx>1?(width-1.0f)/(dx-1) :0):(float)width/dx,
          sy = (border_condition<0 && dy>height)?(dy>1?(height-1.0f)/(dy-1):0):(float)height/dy,
          sz = (border_condition<0 && dz>depth )?(dz>1?(depth-1.0f)/(dz-1) :0):(float)depth/dz,
          sv = (border_condition<0 && dv>dim   )?(dv>1?(dim-1.0f)/(dv-1)   :0):(float)dim/dv;
        res.assign(dx,dy,dz,dv);
        T *ptrd = res.ptr();
        float cx, cy, cz, ck = 0;
        cimg_forV(res,k) { cz = 0;
        cimg_forZ(res,z) { cy = 0;
        cimg_forY(res,y) { cx = 0;
        cimg_forX(res,x) {
          *(ptrd++) = (T)(border_condition?cubic_pix2d(cx,cy,(int)cz,(int)ck):cubic_pix2d(cx,cy,(int)cz,(int)ck,0));
          cx+=sx;
        } cy+=sy;
        } cz+=sz;
        } ck+=sv;
        }
      } break;

      default: // Invalid interpolation method
        throw CImgArgumentException("CImg<%s>::resize() : Invalid interpolation method : %d specified.",
                                    pixel_type(),interp);
      }
      return res;
    }

    //! In-place version of the previous function.
    CImg<T>& resize(const int pdx=-100, const int pdy=-100, const int pdz=-100, const int pdv=-100,
                    const int interp=1, const int border_condition=-1, const bool center=false) {
      if (!pdx || !pdy || !pdz || !pdv) return assign();
      const unsigned int
        dx = pdx<0?-pdx*width/100:pdx,
        dy = pdy<0?-pdy*height/100:pdy,
        dz = pdz<0?-pdz*depth/100:pdz,
        dv = pdv<0?-pdv*dim/100:pdv;
      if (width==dx && height==dy && depth==dz && dim==dv) return *this;
      if (interp==-1 && dx*dy*dz*dv==size()) {
	width = dx; height = dy; depth = dz; dim = dv;
	return *this;
      }
      return get_resize(dx,dy,dz,dv,interp,border_condition,center).transfer_to(*this);
    }

    //! Return a resized image.
    /**
       \param src  Image giving the geometry of the resize.
       \param interp  Interpolation method :
       - 1 = raw memory
       - 0 = no interpolation : additional space is filled with 0.
       - 1 = bloc interpolation (nearest point).
       - 2 = mosaic : image is repeated if necessary.
       - 3 = linear interpolation.
       - 4 = grid interpolation.
       - 5 = bi-cubic interpolation.
       \param border_condition Border condition type.
       \note If pd[x,y,z,v]<0, it corresponds to a percentage of the original size (the default value is -100).
    **/
    template<typename t> CImg<T> get_resize(const CImg<t>& src, const int interp=1,
                                            const int border_condition=-1, const bool center=false) const {
      return get_resize(src.width,src.height,src.depth,src.dim,interp,border_condition,center);
    }

    //! In-place version of the previous function.
    template<typename t> CImg<T>& resize(const CImg<t>& src, const int interp=1,
                                         const int border_condition=-1, const bool center=false) {
      return resize(src.width,src.height,src.depth,src.dim,interp,border_condition,center);
    }

    //! Return a resized image.
    /**
       \param disp = Display giving the geometry of the resize.
       \param interp = Resizing type :
       - 0 = no interpolation : additional space is filled with 0.
       - 1 = bloc interpolation (nearest point).
       - 2 = mosaic : image is repeated if necessary.
       - 3 = linear interpolation.
       - 4 = grid interpolation.
       - 5 = bi-cubic interpolation.
       - 6 = moving average (best quality for photographs)
       \param border_condition Border condition type.
       \note If pd[x,y,z,v]<0, it corresponds to a percentage of the original size (the default value is -100).
    **/
    CImg<T> get_resize(const CImgDisplay& disp, const int interp=1,
                       const int border_condition=-1, const bool center=false) const {
      return get_resize(disp.width,disp.height,depth,dim,interp,border_condition,center);
    }

    //! In-place version of the previous function.
    CImg<T>& resize(const CImgDisplay& disp, const int interp=1,
                    const int border_condition=-1, const bool center=false) {
      return resize(disp.width,disp.height,depth,dim,interp,border_condition,center);
    }

    //! Return an half-resized image, using a special filter.
    CImg<T> get_resize_halfXY() const {
      typedef typename cimg::superset<T,float>::type ftype;
      if (is_empty()) return CImg<T>();
      const ftype mask[9] = { 0.07842776544f, 0.1231940459f, 0.07842776544f,
                              0.1231940459f,  0.1935127547f, 0.1231940459f,
                              0.07842776544f, 0.1231940459f, 0.07842776544f };
      ftype I[9] = { 0 };
      CImg<T> dest(width/2,height/2,depth,dim);
      cimg_forZV(*this,z,k) cimg_for3x3(*this,x,y,z,k,I)
        if (x%2 && y%2) dest(x/2,y/2,z,k) = (T)
                          (I[0]*mask[0] + I[1]*mask[1] + I[2]*mask[2] +
                           I[3]*mask[3] + I[4]*mask[4] + I[5]*mask[5] +
                           I[6]*mask[6] + I[7]*mask[7] + I[8]*mask[8]);
      return dest;
    }

    //! In-place version of the previous function.
    CImg<T>& resize_halfXY() {
      return get_resize_halfXY().transfer_to(*this);
    }

    //! Upscale an image by a factor 2x.
    /**
       Use anisotropic upscaling algorithm described at
       http://scale2x.sourceforge.net/algorithm.html
    **/
    CImg<T> get_resize_doubleXY() const {

#define cimg_gs2x_for3(bound,i) \
 for (int i = 0, _p1##i = 0, \
      _n1##i = 1>=(bound)?(int)(bound)-1:1; \
      _n1##i<(int)(bound) || i==--_n1##i; \
      _p1##i = i++, ++_n1##i, ptrd1+=(res).width, ptrd2+=(res).width)

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

      if (is_empty()) return *this;
      CImg<T> res(2*width,2*height,depth,dim);
      CImg_3x3(I,T);
      cimg_forZV(*this,z,k) {
        T
          *ptrd1 = res.ptr(0,0,0,k),
          *ptrd2 = ptrd1 + res.width;
        cimg_gs2x_for3x3(*this,x,y,0,k,I) {
          if (Icp!=Icn && Ipc!=Inc) {
            *(ptrd1++) = Ipc==Icp?Ipc:Icc;
            *(ptrd1++) = Icp==Inc?Inc:Icc;
            *(ptrd2++) = Ipc==Icn?Ipc:Icc;
            *(ptrd2++) = Icn==Inc?Inc:Icc;
          } else { *(ptrd1++) = Icc; *(ptrd1++) = Icc; *(ptrd2++) = Icc; *(ptrd2++) = Icc; }
        }
      }
      return res;
    }

    //! In-place version of the previous function
    CImg<T>& resize_doubleXY() {
      return get_resize_doubleXY().transfer_to(*this);
    }

    //! Upscale an image by a factor 3x.
    /**
       Use anisotropic upscaling algorithm described at
       http://scale2x.sourceforge.net/algorithm.html
    **/
    CImg<T> get_resize_tripleXY() const {

#define cimg_gs3x_for3(bound,i) \
 for (int i = 0, _p1##i = 0, \
      _n1##i = 1>=(bound)?(int)(bound)-1:1; \
      _n1##i<(int)(bound) || i==--_n1##i; \
      _p1##i = i++, ++_n1##i, ptrd1+=2*(res).width, ptrd2+=2*(res).width, ptrd3+=2*(res).width)

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

      if (is_empty()) return *this;
      CImg<T> res(3*width,3*height,depth,dim);
      CImg_3x3(I,T);
      cimg_forZV(*this,z,k) {
        T
          *ptrd1 = res.ptr(0,0,0,k),
          *ptrd2 = ptrd1 + res.width,
          *ptrd3 = ptrd2 + res.width;
        cimg_gs3x_for3x3(*this,x,y,0,k,I) {
          if (Icp != Icn && Ipc != Inc) {
            *(ptrd1++) = Ipc==Icp?Ipc:Icc;
            *(ptrd1++) = (Ipc==Icp && Icc!=Inp) || (Icp==Inc && Icc!=Ipp)?Icp:Icc;
            *(ptrd1++) = Icp==Inc?Inc:Icc;
            *(ptrd2++) = (Ipc==Icp && Icc!=Ipn) || (Ipc==Icn && Icc!=Ipp)?Ipc:Icc;
            *(ptrd2++) = Icc;
            *(ptrd2++) = (Icp==Inc && Icc!=Inn) || (Icn==Inc && Icc!=Inp)?Inc:Icc;
            *(ptrd3++) = Ipc==Icn?Ipc:Icc;
            *(ptrd3++) = (Ipc==Icn && Icc!=Inn) || (Icn==Inc && Icc!=Ipn)?Icn:Icc;
            *(ptrd3++) = Icn==Inc?Inc:Icc;
          } else {
            *(ptrd1++) = Icc; *(ptrd1++) = Icc; *(ptrd1++) = Icc;
            *(ptrd2++) = Icc; *(ptrd2++) = Icc; *(ptrd2++) = Icc;
            *(ptrd3++) = Icc; *(ptrd3++) = Icc; *(ptrd3++) = Icc;
          }
        }
      }
      return res;
    }

    //! In-place version of the previous function
    CImg<T>& resize_tripleXY() {
      return get_resize_tripleXY().transfer_to(*this);
    }

    //! Permute axes order.
    /**
       This function permutes image axes.
       \param permut = String describing the permutation (4 characters).
    **/
    template<typename t> CImg<t> get_permute_axes(const char *permut, const t&) const {
      if (is_empty() || !permut) return CImg<t>(*this,false);
      CImg<t> res;
      const T* ptrs = data;
      if (!cimg::strncasecmp(permut,"xyzv",4)) return (+*this);
      if (!cimg::strncasecmp(permut,"xyvz",4)) {
	res.assign(width,height,dim,depth);
	cimg_forXYZV(*this,x,y,z,v) res(x,y,v,z) = (t)*(ptrs++);
      }
      if (!cimg::strncasecmp(permut,"xzyv",4)) {
	res.assign(width,depth,height,dim);
	cimg_forXYZV(*this,x,y,z,v) res(x,z,y,v) = (t)*(ptrs++);
      }
      if (!cimg::strncasecmp(permut,"xzvy",4)) {
	res.assign(width,depth,dim,height);
	cimg_forXYZV(*this,x,y,z,v) res(x,z,v,y) = (t)*(ptrs++);
      }
      if (!cimg::strncasecmp(permut,"xvyz",4)) {
	res.assign(width,dim,height,depth);
	cimg_forXYZV(*this,x,y,z,v) res(x,v,y,z) = (t)*(ptrs++);
      }
      if (!cimg::strncasecmp(permut,"xvzy",4)) {
	res.assign(width,dim,depth,height);
	cimg_forXYZV(*this,x,y,z,v) res(x,v,z,y) = (t)*(ptrs++);
      }
      if (!cimg::strncasecmp(permut,"yxzv",4)) {
	res.assign(height,width,depth,dim);
	cimg_forXYZV(*this,x,y,z,v) res(y,x,z,v) = (t)*(ptrs++);
      }
      if (!cimg::strncasecmp(permut,"yxvz",4)) {
	res.assign(height,width,dim,depth);
	cimg_forXYZV(*this,x,y,z,v) res(y,x,v,z) = (t)*(ptrs++);
      }
      if (!cimg::strncasecmp(permut,"yzxv",4)) {
	res.assign(height,depth,width,dim);
	cimg_forXYZV(*this,x,y,z,v) res(y,z,x,v) = (t)*(ptrs++);
      }
      if (!cimg::strncasecmp(permut,"yzvx",4)) {
	res.assign(height,depth,dim,width);
	switch (width) {
	case 1: {
	  t *ptrR = res.ptr(0,0,0,0);
	  for (unsigned long siz = height*depth*dim+1; siz; --siz) {
	    *(ptrR++) = (t)*(ptrs++);
	  }
	} break;
	case 2: {
	  t *ptrR = res.ptr(0,0,0,0), *ptrG = res.ptr(0,0,0,1);
	  for (unsigned long siz = height*depth*dim+1; siz; --siz) {
	    *(ptrR++) = (t)*(ptrs++); *(ptrG++) = (t)*(ptrs++);
	  }
	} break;
	case 3: { // Optimization for the classical conversion from interleaved RGB to planar RGB
	  t *ptrR = res.ptr(0,0,0,0), *ptrG = res.ptr(0,0,0,1), *ptrB = res.ptr(0,0,0,2);
	  for (unsigned long siz = height*depth*dim+1; siz; --siz) {
	    *(ptrR++) = (t)*(ptrs++); *(ptrG++) = (t)*(ptrs++); *(ptrB++) = (t)*(ptrs++);
	  }
	} break;
	case 4: { // Optimization for the classical conversion from interleaved RGBA to planar RGBA
	  t *ptrR = res.ptr(0,0,0,0), *ptrG = res.ptr(0,0,0,1), *ptrB = res.ptr(0,0,0,2), *ptrA = res.ptr(0,0,0,3);
	  for (unsigned long siz = height*depth*dim+1; siz; --siz) {
	    *(ptrR++) = (t)*(ptrs++); *(ptrG++) = (t)*(ptrs++); *(ptrB++) = (t)*(ptrs++); *(ptrA++) = (t)*(ptrs++);
	  }
	} break;
	default: {
	  cimg_forXYZV(*this,x,y,z,v) res(y,z,v,x) = *(ptrs++); return res;
	} break;
	}
      }
      if (!cimg::strncasecmp(permut,"yvxz",4)) {
	res.assign(height,dim,width,depth);
	cimg_forXYZV(*this,x,y,z,v) res(y,v,x,z) = (t)*(ptrs++);
      }
      if (!cimg::strncasecmp(permut,"yvzx",4)) {
	res.assign(height,dim,depth,width);
	cimg_forXYZV(*this,x,y,z,v) res(y,v,z,x) = (t)*(ptrs++);
      }
      if (!cimg::strncasecmp(permut,"zxyv",4)) {
	res.assign(depth,width,height,dim);
	cimg_forXYZV(*this,x,y,z,v) res(z,x,y,v) = (t)*(ptrs++);
      }
      if (!cimg::strncasecmp(permut,"zxvy",4)) {
	res.assign(depth,width,dim,height);
	cimg_forXYZV(*this,x,y,z,v) res(z,x,v,y) = (t)*(ptrs++);
      }
      if (!cimg::strncasecmp(permut,"zyxv",4)) {
	res.assign(depth,height,width,dim);
	cimg_forXYZV(*this,x,y,z,v) res(z,y,x,v) = (t)*(ptrs++);
      }
      if (!cimg::strncasecmp(permut,"zyvx",4)) {
	res.assign(depth,height,dim,width);
	cimg_forXYZV(*this,x,y,z,v) res(z,y,v,x) = (t)*(ptrs++);
      }
      if (!cimg::strncasecmp(permut,"zvxy",4)) {
	res.assign(depth,dim,width,height);
	cimg_forXYZV(*this,x,y,z,v) res(z,v,x,y) = (t)*(ptrs++);
      }
      if (!cimg::strncasecmp(permut,"zvyx",4)) {
	res.assign(depth,dim,height,width);
	cimg_forXYZV(*this,x,y,z,v) res(z,v,y,x) = (t)*(ptrs++);
      }
      if (!cimg::strncasecmp(permut,"vxyz",4)) {
	res.assign(dim,width,height,depth);
	switch (dim) {
	case 1: {
	  const T *ptrR = ptr(0,0,0,0);
	  t *ptrd = res.ptr();
	  for (unsigned long siz = width*height*depth+1; siz; --siz) {
	    *(ptrd++) = (t)*(ptrR++);
	  }
	} break;
	case 2: {
	  const T *ptrR = ptr(0,0,0,0), *ptrG = ptr(0,0,0,1);
	  t *ptrd = res.ptr();
	  for (unsigned long siz = width*height*depth+1; siz; --siz) {
	    *(ptrd++) = (t)*(ptrR++); *(ptrd++) = (t)*(ptrG++);
	  }
	} break;
	case 3: { // Optimization for the classical conversion from planar RGB to interleaved RGB
	  const T *ptrR = ptr(0,0,0,0), *ptrG = ptr(0,0,0,1), *ptrB = ptr(0,0,0,2);
	  t *ptrd = res.ptr();
	  for (unsigned long siz = width*height*depth+1; siz; --siz) {
	    *(ptrd++) = (t)*(ptrR++); *(ptrd++) = (t)*(ptrG++); *(ptrd++) = (t)*(ptrB++);
	  }
	} break;
	case 4: { // Optimization for the classical conversion from planar RGBA to interleaved RGBA
	  const T *ptrR = ptr(0,0,0,0), *ptrG = ptr(0,0,0,1), *ptrB = ptr(0,0,0,2), *ptrA = ptr(0,0,0,3);
	  t *ptrd = res.ptr();
	  for (unsigned long siz = width*height*depth+1; siz; --siz) {
	    *(ptrd++) = (t)*(ptrR++); *(ptrd++) = (t)*(ptrG++); *(ptrd++) = (t)*(ptrB++); *(ptrd++) = (t)*(ptrA++);
	  }
	} break;
	default: {
	  cimg_forXYZV(*this,x,y,z,v) res(v,x,y,z) = (t)*(ptrs++);
	} break;
	}
      }
      if (!cimg::strncasecmp(permut,"vxzy",4)) {
	res.assign(dim,width,depth,height);
	cimg_forXYZV(*this,x,y,z,v) res(v,x,z,y) = (t)*(ptrs++);
      }
      if (!cimg::strncasecmp(permut,"vyxz",4)) {
	res.assign(dim,height,width,depth);
	cimg_forXYZV(*this,x,y,z,v) res(v,y,x,z) = (t)*(ptrs++);
      }
      if (!cimg::strncasecmp(permut,"vyzx",4)) {
	res.assign(dim,height,depth,width);
	cimg_forXYZV(*this,x,y,z,v) res(v,y,z,x) = (t)*(ptrs++);
      }
      if (!cimg::strncasecmp(permut,"vzxy",4)) {
	res.assign(dim,depth,width,height);
	cimg_forXYZV(*this,x,y,z,v) res(v,z,x,y) = (t)*(ptrs++);
      }
      if (!cimg::strncasecmp(permut,"vzyx",4)) {
	res.assign(dim,depth,height,width);
	cimg_forXYZV(*this,x,y,z,v) res(v,z,y,x) = (t)*(ptrs++);
      }
      if (!res) throw CImgArgumentException("CImg<%s>::permute_axes() : Invalid input permutation '%s'.",pixel_type(),permut);
      return res;
    }

    //! Permute axes order.
    CImg<T> get_permute_axes(const char *permut="vxyz") const {
      const T foo = (T)0;
      return get_permute_axes(permut,foo);
    }

    //! In-place version of the previous function.
    CImg<T>& permute_axes(const char *order="vxyz") {
      return get_permute_axes(order).transfer_to(*this);
    }

    //! Mirror an image along the specified axis.
    CImg<T> get_mirror(const char axe='x') const {
      return (+*this).mirror(axe);
    }

    //! In-place version of the previous function.
    CImg<T>& mirror(const char axe='x') {
      if (!is_empty()) {
        T *pf, *pb, *buf = 0;
        switch (cimg::uncase(axe)) {
        case 'x': {
          pf = data; pb = ptr(width-1);
          for (unsigned int yzv=0; yzv<height*depth*dim; ++yzv) {
            for (unsigned int x=0; x<width/2; ++x) { const T val = *pf; *(pf++) = *pb; *(pb--) = val; }
            pf+=width-width/2;
            pb+=width+width/2;
          }
        } break;
        case 'y': {
          buf = new T[width];
          pf = data; pb = ptr(0,height-1);
          for (unsigned int zv=0; zv<depth*dim; ++zv) {
            for (unsigned int y=0; y<height/2; ++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-height/2);
            pb+=width*(height+height/2);
          }
        } break;
        case 'z': {
          buf = new T[width*height];
          pf = data; pb = ptr(0,0,depth-1);
          cimg_forV(*this,v) {
            for (unsigned int z=0; z<depth/2; ++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-depth/2);
            pb+=width*height*(depth+depth/2);
          }
        } break;
        case 'v': {
          buf = new T[width*height*depth];
          pf = data; pb = ptr(0,0,0,dim-1);
          for (unsigned int v=0; v<dim/2; ++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<%s>::mirror() : unknown axe '%c', must be 'x','y','z' or 'v'",pixel_type(),axe);
        }
        if (buf) delete[] buf;
      }
      return *this;
    }

    //! Translate the image.
    /**
       \param deltax Amount of displacement along the X-axis.
       \param deltay Amount of displacement along the Y-axis.
       \param deltaz Amount of displacement along the Z-axis.
       \param deltav Amount of displacement along the V-axis.
       \param border_condition Border condition.

       - \c border_condition can be :
          - 0 : Zero border condition (Dirichlet).
          - 1 : Nearest neighbors (Neumann).
          - 2 : Repeat Pattern (Fourier style).
    **/
    CImg<T> get_translate(const int deltax, const int deltay=0, const int deltaz=0, const int deltav=0,
                          const int border_condition=0) const {
      return (+*this).translate(deltax,deltay,deltaz,deltav,border_condition);
    }

    //! In-place version of the previous function.
    CImg<T>& translate(const int deltax, const int deltay=0, const int deltaz=0, const int deltav=0,
                       const int border_condition=0) {
      if (!is_empty()) {

        if (deltax) // Translate along X-axis
          switch (border_condition) {
          case 0:
            if (cimg::abs(deltax)>=dimx()) return fill(0);
            if (deltax>0) cimg_forYZV(*this,y,z,k) {
              std::memmove(ptr(0,y,z,k),ptr(deltax,y,z,k),(width-deltax)*sizeof(T));
              std::memset(ptr(width-deltax,y,z,k),0,deltax*sizeof(T));
            } else cimg_forYZV(*this,y,z,k) {
              std::memmove(ptr(-deltax,y,z,k),ptr(0,y,z,k),(width+deltax)*sizeof(T));
              std::memset(ptr(0,y,z,k),0,-deltax*sizeof(T));
            }
            break;
          case 1:
            if (deltax>0) {
              const int ndeltax = (deltax>=dimx())?width-1:deltax;
              if (!ndeltax) return *this;
              cimg_forYZV(*this,y,z,k) {
                std::memmove(ptr(0,y,z,k),ptr(ndeltax,y,z,k),(width-ndeltax)*sizeof(T));
                T *ptrd = ptr(width-1,y,z,k);
                const T val = *ptrd;
                for (int l=0; l<ndeltax-1; ++l) *(--ptrd) = val;
              }
            } else {
              const int ndeltax = (-deltax>=dimx())?width-1:-deltax;
              if (!ndeltax) return *this;
              cimg_forYZV(*this,y,z,k) {
                std::memmove(ptr(ndeltax,y,z,k),ptr(0,y,z,k),(width-ndeltax)*sizeof(T));
                T *ptrd = ptr(0,y,z,k);
                const T val = *ptrd;
                for (int l=0; l<ndeltax-1; ++l) *(++ptrd) = val;
              }
            }
            break;
          case 2: {
            const int ml = cimg::mod(deltax,dimx()), ndeltax = (ml<=dimx()/2)?ml:(ml-dimx());
            if (!ndeltax) return *this;
            T* buf = new T[cimg::abs(ndeltax)];
            if (ndeltax>0) cimg_forYZV(*this,y,z,k) {
              std::memcpy(buf,ptr(0,y,z,k),ndeltax*sizeof(T));
              std::memmove(ptr(0,y,z,k),ptr(ndeltax,y,z,k),(width-ndeltax)*sizeof(T));
              std::memcpy(ptr(width-ndeltax,y,z,k),buf,ndeltax*sizeof(T));
            } else cimg_forYZV(*this,y,z,k) {
              std::memcpy(buf,ptr(width+ndeltax,y,z,k),-ndeltax*sizeof(T));
              std::memmove(ptr(-ndeltax,y,z,k),ptr(0,y,z,k),(width+ndeltax)*sizeof(T));
              std::memcpy(ptr(0,y,z,k),buf,-ndeltax*sizeof(T));
            }
            delete[] buf;
          } break;
          }

        if (deltay) // Translate along Y-axis
          switch (border_condition) {
          case 0:
            if (cimg::abs(deltay)>=dimy()) return fill(0);
            if (deltay>0) cimg_forZV(*this,z,k) {
              std::memmove(ptr(0,0,z,k),ptr(0,deltay,z,k),width*(height-deltay)*sizeof(T));
              std::memset(ptr(0,height-deltay,z,k),0,width*deltay*sizeof(T));
            } else cimg_forZV(*this,z,k) {
              std::memmove(ptr(0,-deltay,z,k),ptr(0,0,z,k),width*(height+deltay)*sizeof(T));
              std::memset(ptr(0,0,z,k),0,-deltay*width*sizeof(T));
            }
            break;
          case 1:
            if (deltay>0) {
              const int ndeltay = (deltay>=dimy())?height-1:deltay;
              if (!ndeltay) return *this;
              cimg_forZV(*this,z,k) {
                std::memmove(ptr(0,0,z,k),ptr(0,ndeltay,z,k),width*(height-ndeltay)*sizeof(T));
                T *ptrd = ptr(0,height-ndeltay,z,k), *ptrs = ptr(0,height-1,z,k);
                for (int l=0; l<ndeltay-1; ++l) { std::memcpy(ptrd,ptrs,width*sizeof(T)); ptrd+=width; }
              }
            } else {
              const int ndeltay = (-deltay>=dimy())?height-1:-deltay;
              if (!ndeltay) return *this;
              cimg_forZV(*this,z,k) {
                std::memmove(ptr(0,ndeltay,z,k),ptr(0,0,z,k),width*(height-ndeltay)*sizeof(T));
                T *ptrd = ptr(0,1,z,k), *ptrs = ptr(0,0,z,k);
                for (int l=0; l<ndeltay-1; ++l) { std::memcpy(ptrd,ptrs,width*sizeof(T)); ptrd+=width; }
              }
            }
            break;
          case 2: {
            const int ml = cimg::mod(deltay,dimy()), ndeltay = (ml<=dimy()/2)?ml:(ml-dimy());
            if (!ndeltay) return *this;
            T* buf = new T[width*cimg::abs(ndeltay)];
            if (ndeltay>0) cimg_forZV(*this,z,k) {
              std::memcpy(buf,ptr(0,0,z,k),width*ndeltay*sizeof(T));
              std::memmove(ptr(0,0,z,k),ptr(0,ndeltay,z,k),width*(height-ndeltay)*sizeof(T));
              std::memcpy(ptr(0,height-ndeltay,z,k),buf,width*ndeltay*sizeof(T));
            } else cimg_forZV(*this,z,k) {
              std::memcpy(buf,ptr(0,height+ndeltay,z,k),-ndeltay*width*sizeof(T));
              std::memmove(ptr(0,-ndeltay,z,k),ptr(0,0,z,k),width*(height+ndeltay)*sizeof(T));
              std::memcpy(ptr(0,0,z,k),buf,-ndeltay*width*sizeof(T));
            }
            delete[] buf;
          } break;
          }

        if (deltaz) // Translate along Z-axis
          switch (border_condition) {
          case 0:
            if (cimg::abs(deltaz)>=dimz()) return fill(0);
            if (deltaz>0) cimg_forV(*this,k) {
              std::memmove(ptr(0,0,0,k),ptr(0,0,deltaz,k),width*height*(depth-deltaz)*sizeof(T));
              std::memset(ptr(0,0,depth-deltaz,k),0,width*height*deltaz*sizeof(T));
            } else cimg_forV(*this,k) {
              std::memmove(ptr(0,0,-deltaz,k),ptr(0,0,0,k),width*height*(depth+deltaz)*sizeof(T));
              std::memset(ptr(0,0,0,k),0,-deltaz*width*height*sizeof(T));
            }
            break;
          case 1:
            if (deltaz>0) {
              const int ndeltaz = (deltaz>=dimz())?depth-1:deltaz;
              if (!ndeltaz) return *this;
              cimg_forV(*this,k) {
                std::memmove(ptr(0,0,0,k),ptr(0,0,ndeltaz,k),width*height*(depth-ndeltaz)*sizeof(T));
                T *ptrd = ptr(0,0,depth-ndeltaz,k), *ptrs = ptr(0,0,depth-1,k);
                for (int l=0; l<ndeltaz-1; ++l) { std::memcpy(ptrd,ptrs,width*height*sizeof(T)); ptrd+=width*height; }
              }
            } else {
              const int ndeltaz = (-deltaz>=dimz())?depth-1:-deltaz;
              if (!ndeltaz) return *this;
              cimg_forV(*this,k) {
                std::memmove(ptr(0,0,ndeltaz,k),ptr(0,0,0,k),width*height*(depth-ndeltaz)*sizeof(T));
                T *ptrd = ptr(0,0,1,k), *ptrs = ptr(0,0,0,k);
                for (int l=0; l<ndeltaz-1; ++l) { std::memcpy(ptrd,ptrs,width*height*sizeof(T)); ptrd+=width*height; }
              }
            }
            break;
          case 2: {
            const int ml = cimg::mod(deltaz,dimz()), ndeltaz = (ml<=dimz()/2)?ml:(ml-dimz());
            if (!ndeltaz) return *this;
            T* buf = new T[width*height*cimg::abs(ndeltaz)];
            if (ndeltaz>0) cimg_forV(*this,k) {
              std::memcpy(buf,ptr(0,0,0,k),width*height*ndeltaz*sizeof(T));
              std::memmove(ptr(0,0,0,k),ptr(0,0,ndeltaz,k),width*height*(depth-ndeltaz)*sizeof(T));
              std::memcpy(ptr(0,0,depth-ndeltaz,k),buf,width*height*ndeltaz*sizeof(T));
            } else cimg_forV(*this,k) {
              std::memcpy(buf,ptr(0,0,depth+ndeltaz,k),-ndeltaz*width*height*sizeof(T));
              std::memmove(ptr(0,0,-ndeltaz,k),ptr(0,0,0,k),width*height*(depth+ndeltaz)*sizeof(T));
              std::memcpy(ptr(0,0,0,k),buf,-ndeltaz*width*height*sizeof(T));
            }
            delete[] buf;
          } break;
          }

        if (deltav) // Translate along V-axis
          switch (border_condition) {
          case 0:
            if (cimg::abs(deltav)>=dimv()) return fill(0);
            if (deltav>0) {
              std::memmove(data,ptr(0,0,0,deltav),width*height*depth*(dim-deltav)*sizeof(T));
              std::memset(ptr(0,0,0,dim-deltav),0,width*height*depth*deltav*sizeof(T));
            } else cimg_forV(*this,k) {
              std::memmove(ptr(0,0,0,-deltav),data,width*height*depth*(dim+deltav)*sizeof(T));
              std::memset(data,0,-deltav*width*height*depth*sizeof(T));
            }
            break;
          case 1:
            if (deltav>0) {
              const int ndeltav = (deltav>=dimv())?dim-1:deltav;
              if (!ndeltav) return *this;
              std::memmove(data,ptr(0,0,0,ndeltav),width*height*depth*(dim-ndeltav)*sizeof(T));
              T *ptrd = ptr(0,0,0,dim-ndeltav), *ptrs = ptr(0,0,0,dim-1);
              for (int l=0; l<ndeltav-1; ++l) { std::memcpy(ptrd,ptrs,width*height*depth*sizeof(T)); ptrd+=width*height*depth; }
            } else {
              const int ndeltav = (-deltav>=dimv())?dim-1:-deltav;
              if (!ndeltav) return *this;
              std::memmove(ptr(0,0,0,ndeltav),data,width*height*depth*(dim-ndeltav)*sizeof(T));
              T *ptrd = ptr(0,0,0,1);
              for (int l=0; l<ndeltav-1; ++l) { std::memcpy(ptrd,data,width*height*depth*sizeof(T)); ptrd+=width*height*depth; }
            }
            break;
          case 2: {
            const int ml = cimg::mod(deltav,dimv()), ndeltav = (ml<=dimv()/2)?ml:(ml-dimv());
            if (!ndeltav) return *this;
            T* buf = new T[width*height*depth*cimg::abs(ndeltav)];
            if (ndeltav>0) {
              std::memcpy(buf,data,width*height*depth*ndeltav*sizeof(T));
              std::memmove(data,ptr(0,0,0,ndeltav),width*height*depth*(dim-ndeltav)*sizeof(T));
              std::memcpy(ptr(0,0,0,dim-ndeltav),buf,width*height*depth*ndeltav*sizeof(T));
            } else {
              std::memcpy(buf,ptr(0,0,0,dim+ndeltav),-ndeltav*width*height*depth*sizeof(T));
              std::memmove(ptr(0,0,0,-ndeltav),data,width*height*depth*(dim+ndeltav)*sizeof(T));
              std::memcpy(data,buf,-ndeltav*width*height*depth*sizeof(T));
            }
            delete[] buf;
          } break;
          }
      }
      return *this;
    }

    //! Return a square region of the image, as a new image
    /**
       \param x0 = X-coordinate of the upper-left crop rectangle corner.
       \param y0 = Y-coordinate of the upper-left crop rectangle corner.
       \param z0 = Z-coordinate of the upper-left crop rectangle corner.
       \param v0 = V-coordinate of the upper-left crop rectangle corner.
       \param x1 = X-coordinate of the lower-right crop rectangle corner.
       \param y1 = Y-coordinate of the lower-right crop rectangle corner.
       \param z1 = Z-coordinate of the lower-right crop rectangle corner.
       \param v1 = V-coordinate of the lower-right crop rectangle corner.
       \param border_condition = Dirichlet (false) or Neumann border conditions.
    **/
    CImg<T> get_crop(const int x0, const int y0, const int z0, const int v0,
                     const int x1, const int y1, const int z1, const int v1,
                     const bool border_condition=false) const {
      if (is_empty()) return *this;
      const int
        nx0 = x0<x1?x0:x1, nx1 = x0^x1^nx0,
        ny0 = y0<y1?y0:y1, ny1 = y0^y1^ny0,
        nz0 = z0<z1?z0:z1, nz1 = z0^z1^nz0,
        nv0 = v0<v1?v0:v1, nv1 = v0^v1^nv0;
      CImg<T> dest(1U+nx1-nx0,1U+ny1-ny0,1U+nz1-nz0,1U+nv1-nv0);
      if (nx0<0 || nx1>=dimx() || ny0<0 || ny1>=dimy() || nz0<0 || nz1>=dimz() || nv0<0 || nv1>=dimv()) {
        if (border_condition) cimg_forXYZV(dest,x,y,z,v) dest(x,y,z,v) = pix4d(nx0+x,ny0+y,nz0+z,nv0+v);
        else dest.fill(0).draw_image(*this,-nx0,-ny0,-nz0,-nv0);
      } else dest.draw_image(*this,-nx0,-ny0,-nz0,-nv0);
      return dest;
    }

    //! In-place version of the previous function.
    CImg<T>& crop(const int x0, const int y0, const int z0, const int v0,
                  const int x1, const int y1, const int z1, const int v1,
                  const bool border_condition=false) {
      return get_crop(x0,y0,z0,v0,x1,y1,z1,v1,border_condition).transfer_to(*this);
    }

    //! Return a square region of the image, as a new image
    /**
       \param x0 = X-coordinate of the upper-left crop rectangle corner.
       \param y0 = Y-coordinate of the upper-left crop rectangle corner.
       \param z0 = Z-coordinate of the upper-left crop rectangle corner.
       \param x1 = X-coordinate of the lower-right crop rectangle corner.
       \param y1 = Y-coordinate of the lower-right crop rectangle corner.
       \param z1 = Z-coordinate of the lower-right crop rectangle corner.
       \param border_condition = determine the type of border condition if
       some of the desired region is outside the image.
    **/
    CImg<T> get_crop(const int x0, const int y0, const int z0,
                     const int x1, const int y1, const int z1,
                     const bool border_condition=false) const {
      return get_crop(x0,y0,z0,0,x1,y1,z1,dim-1,border_condition);
    }

    //! In-place version of the previous function.
    CImg<T>& crop(const int x0, const int y0, const int z0,
                  const int x1, const int y1, const int z1,
                  const bool border_condition=false) {
      return crop(x0,y0,z0,0,x1,y1,z1,dim-1,border_condition);
    }

    //! Return a square region of the image, as a new image
    /**
       \param x0 = X-coordinate of the upper-left crop rectangle corner.
       \param y0 = Y-coordinate of the upper-left crop rectangle corner.
       \param x1 = X-coordinate of the lower-right crop rectangle corner.
       \param y1 = Y-coordinate of the lower-right crop rectangle corner.
       \param border_condition = determine the type of border condition if
       some of the desired region is outside the image.
    **/
    CImg<T> get_crop(const int x0, const int y0,
                     const int x1, const int y1,
                     const bool border_condition=false) const {
      return get_crop(x0,y0,0,0,x1,y1,depth-1,dim-1,border_condition);
    }

    //! In-place version of the previous function.
    CImg<T>& crop(const int x0, const int y0,
                  const int x1, const int y1,
                  const bool border_condition=false) {
      return crop(x0,y0,0,0,x1,y1,depth-1,dim-1,border_condition);
    }

    //! Return a square region of the image, as a new image
    /**
       \param x0 = X-coordinate of the upper-left crop rectangle corner.
       \param x1 = X-coordinate of the lower-right crop rectangle corner.
       \param border_condition = determine the type of border condition if
       some of the desired region is outside the image.
    **/
    CImg<T> get_crop(const int x0, const int x1, const bool border_condition=false) const {
      return get_crop(x0,0,0,0,x1,height-1,depth-1,dim-1,border_condition);
    }

    //! In-place version of the previous function.
    CImg<T>& crop(const int x0, const int x1, const bool border_condition=false) {
      return crop(x0,0,0,0,x1,height-1,depth-1,dim-1,border_condition);
    }

    //! Return a set of columns
    CImg<T> get_columns(const unsigned int x0, const unsigned int x1) const {
      return get_crop((int)x0,0,0,0,(int)x1,dimy()-1,dimz()-1,dimv()-1);
    }

    //! In-place version of the previous function.
    CImg<T>& columns(const unsigned int x0, const unsigned int x1) {
      return get_columns(x0,x1).transfer_to(*this);
    }

    //! Return one column
    CImg<T> get_column(const unsigned int x0) const {
      return get_columns(x0,x0);
    }

    //! In-place version of the previous function.
    CImg<T>& column(const unsigned int x0) {
      return columns(x0,x0);
    }

    //! Get a copy of a set of lines of the instance image.
    CImg<T> get_lines(const unsigned int y0, const unsigned int y1) const {
      return get_crop(0,(int)y0,0,0,dimx()-1,(int)y1,dimz()-1,dimv()-1);
    }

    //! In-place version of the previous function.
    CImg<T>& lines(const unsigned int y0, const unsigned int y1) {
      return get_lines(y0,y1).transfer_to(*this);
    }

    //! Get a copy of a line of the instance image.
    CImg<T> get_line(const unsigned int y0) const {
      return get_lines(y0,y0);
    }

    //! In-place version of the previous function.
    CImg<T>& line(const unsigned int y0) {
      return lines(y0,y0);
    }

    //! Get a set of slices
    CImg<T> get_slices(const unsigned int z0, const unsigned int z1) const {
      return get_crop(0,0,(int)z0,0,dimx()-1,dimy()-1,(int)z1,dimv()-1);
    }

    //! In-place version of the previous function.
    CImg<T>& slices(const unsigned int z0, const unsigned int z1) {
      return get_slices(z0,z1).transfer_to(*this);
    }

    //! Get the z-slice \a z of *this, as a new image.
    CImg<T> get_slice(const unsigned int z0) const {
      return get_slices(z0,z0);
    }

    //! In-place version of the previous function.
    CImg<T>& slice(const unsigned int z0) {
      return slices(z0,z0);
    }

    //! Return a copy of a set of channels of the instance image.
    CImg<T> get_channels(const unsigned int v0, const unsigned int v1) const {
      return get_crop(0,0,0,(int)v0,dimx()-1,dimy()-1,dimz()-1,(int)v1);
    }

    //! In-place version of the previous function.
    CImg<T>& channels(const unsigned int v0, const unsigned int v1) {
      return get_channels(v0,v1).transfer_to(*this);
    }

    //! Return a copy of a channel of the instance image.
    CImg<T> get_channel(const unsigned int v0) const {
      return get_channels(v0,v0);
    }

    //! In-place version of the previous function.
    CImg<T>& channel(const unsigned int v0) {
      return channels(v0,v0);
    }

    //! Get a shared-memory image referencing a set of points of the instance image.
    CImg<T> get_shared_points(const unsigned int x0, const unsigned int x1,
                           const unsigned int y0=0, const unsigned int z0=0, const unsigned int v0=0) {
      const unsigned long beg = offset(x0,y0,z0,v0), end = offset(x1,y0,z0,v0);
      if (beg>end || beg>=size() || end>=size())
        throw CImgArgumentException("CImg<%s>::get_shared_points() : Cannot return a shared-memory subset (%u->%u,%u,%u,%u) from "
                                    "a (%u,%u,%u,%u) image.",pixel_type(),x0,x1,y0,z0,v0,width,height,depth,dim);
      return CImg<T>(data+beg,x1-x0+1,1,1,1,true);
    }

    //! Get a shared-memory image referencing a set of points of the instance image (const version).
    const CImg<T> get_shared_points(const unsigned int x0, const unsigned int x1,
                                 const unsigned int y0=0, const unsigned int z0=0, const unsigned int v0=0) const {
      const unsigned long beg = offset(x0,y0,z0,v0), end = offset(x1,y0,z0,v0);
      if (beg>end || beg>=size() || end>=size())
        throw CImgArgumentException("CImg<%s>::get_shared_points() : Cannot return a shared-memory subset (%u->%u,%u,%u,%u) from "
                                    "a (%u,%u,%u,%u) image.",pixel_type(),x0,x1,y0,z0,v0,width,height,depth,dim);
      return CImg<T>(data+beg,x1-x0+1,1,1,1,true);
    }

    //! Return a shared-memory image referencing a set of lines of the instance image.
    CImg<T> get_shared_lines(const unsigned int y0, const unsigned int y1,
                          const unsigned int z0=0, const unsigned int v0=0) {
      const unsigned long beg = offset(0,y0,z0,v0), end = offset(0,y1,z0,v0);
      if (beg>end || beg>=size() || end>=size())
        throw CImgArgumentException("CImg<%s>::get_shared_lines() : Cannot return a shared-memory subset (0->%u,%u->%u,%u,%u) from "
                                    "a (%u,%u,%u,%u) image.",pixel_type(),width-1,y0,y1,z0,v0,width,height,depth,dim);
      return CImg<T>(data+beg,width,y1-y0+1,1,1,true);
    }

    //! Return a shared-memory image referencing a set of lines of the instance image (const version).
    const CImg<T> get_shared_lines(const unsigned int y0, const unsigned int y1,
                                const unsigned int z0=0, const unsigned int v0=0) const {
      const unsigned long beg = offset(0,y0,z0,v0), end = offset(0,y1,z0,v0);
      if (beg>end || beg>=size() || end>=size())
        throw CImgArgumentException("CImg<%s>::get_shared_lines() : Cannot return a shared-memory subset (0->%u,%u->%u,%u,%u) from "
                                    "a (%u,%u,%u,%u) image.",pixel_type(),width-1,y0,y1,z0,v0,width,height,depth,dim);
      return CImg<T>(data+beg,width,y1-y0+1,1,1,true);
    }

    //! Return a shared-memory image referencing one particular line (y0,z0,v0) of the instance image.
    CImg<T> get_shared_line(const unsigned int y0, const unsigned int z0=0, const unsigned int v0=0) {
      return get_shared_lines(y0,y0,z0,v0);
    }

    //! Return a shared-memory image referencing one particular line (y0,z0,v0) of the instance image (const version).
    const CImg<T> get_shared_line(const unsigned int y0, const unsigned int z0=0, const unsigned int v0=0) const {
      return get_shared_lines(y0,y0,z0,v0);
    }

    //! Return a shared memory image referencing a set of planes (z0->z1,v0) of the instance image.
    CImg<T> get_shared_planes(const unsigned int z0, const unsigned int z1, const unsigned int v0=0) {
      const unsigned long beg = offset(0,0,z0,v0), end = offset(0,0,z1,v0);
      if (beg>end || beg>=size() || end>=size())
        throw CImgArgumentException("CImg<%s>::get_shared_planes() : Cannot return a shared-memory subset (0->%u,0->%u,%u->%u,%u) from "
                                    "a (%u,%u,%u,%u) image.",pixel_type(),width-1,height-1,z0,z1,v0,width,height,depth,dim);
      return CImg<T>(data+beg,width,height,z1-z0+1,1,true);
    }

    //! Return a shared-memory image referencing a set of planes (z0->z1,v0) of the instance image (const version).
    const CImg<T> get_shared_planes(const unsigned int z0, const unsigned int z1, const unsigned int v0=0) const {
      const unsigned long beg = offset(0,0,z0,v0), end = offset(0,0,z1,v0);
      if (beg>end || beg>=size() || end>=size())
        throw CImgArgumentException("CImg<%s>::get_shared_planes() : Cannot return a shared-memory subset (0->%u,0->%u,%u->%u,%u) from "
                                    "a (%u,%u,%u,%u) image.",pixel_type(),width-1,height-1,z0,z1,v0,width,height,depth,dim);
      return CImg<T>(data+beg,width,height,z1-z0+1,1,true);
    }

    //! Return a shared-memory image referencing one plane (z0,v0) of the instance image.
    CImg<T> get_shared_plane(const unsigned int z0, const unsigned int v0=0) {
      return get_shared_planes(z0,z0,v0);
    }

    //! Return a shared-memory image referencing one plane (z0,v0) of the instance image (const version).
    const CImg<T> get_shared_plane(const unsigned int z0, const unsigned int v0=0) const {
      return get_shared_planes(z0,z0,v0);
    }

    //! Return a shared-memory image referencing a set of channels (v0->v1) of the instance image.
    CImg<T> get_shared_channels(const unsigned int v0, const unsigned int v1) {
      const unsigned long beg = offset(0,0,0,v0), end = offset(0,0,0,v1);
      if (beg>end || beg>=size() || end>=size())
        throw CImgArgumentException("CImg<%s>::get_shared_channels() : Cannot return a shared-memory subset (0->%u,0->%u,0->%u,%u->%u) from "
                                    "a (%u,%u,%u,%u) image.",pixel_type(),width-1,height-1,depth-1,v0,v1,width,height,depth,dim);
      return CImg<T>(data+beg,width,height,depth,v1-v0+1,true);
    }

    //! Return a shared-memory image referencing a set of channels (v0->v1) of the instance image (const version).
    const CImg<T> get_shared_channels(const unsigned int v0, const unsigned int v1) const {
      const unsigned long beg = offset(0,0,0,v0), end = offset(0,0,0,v1);
      if (beg>end || beg>=size() || end>=size())
        throw CImgArgumentException("CImg<%s>::get_shared_channels() : Cannot return a shared-memory subset (0->%u,0->%u,0->%u,%u->%u) from "
                                    "a (%u,%u,%u,%u) image.",pixel_type(),width-1,height-1,depth-1,v0,v1,width,height,depth,dim);
      return CImg<T>(data+beg,width,height,depth,v1-v0+1,true);
    }

    //! Return a shared-memory image referencing one channel v0 of the instance image.
    CImg<T> get_shared_channel(const unsigned int v0) {
      return get_shared_channels(v0,v0);
    }

    //! Return a shared-memory image referencing one channel v0 of the instance image (const version).
    const CImg<T> get_shared_channel(const unsigned int v0) const {
      return get_shared_channels(v0,v0);
    }

    //! Return a shared version of the instance image.
    CImg<T> get_shared() {
      return CImg<T>(data,width,height,depth,dim,true);
    }

    //! Return a shared version of the instance image (const version).
    const CImg<T> get_shared() const {
      return CImg<T>(data,width,height,depth,dim,true);
    }

    //! Return a 2D representation of a 3D image, with three slices.
    CImg<T> get_projections2d(const unsigned int x0, const unsigned int y0, const unsigned int z0,
                              const int dx=-100, const int dy=-100, const int dz=-100) const {
      if (is_empty()) return CImg<T>();
      const unsigned int
        nx0 = (x0>=width)?width-1:x0,
        ny0 = (y0>=height)?height-1:y0,
        nz0 = (z0>=depth)?depth-1:z0;
      CImg<T>
        imgxy(width,height,1,dim),
        imgzy(depth,height,1,dim),
        imgxz(width,depth,1,dim);
      { cimg_forXYV(*this,x,y,k) imgxy(x,y,k) = (*this)(x,y,nz0,k); }
      { cimg_forYZV(*this,y,z,k) imgzy(z,y,k) = (*this)(nx0,y,z,k); }
      { cimg_forXZV(*this,x,z,k) imgxz(x,z,k) = (*this)(x,ny0,z,k); }
      imgxy.resize(dx,dy,1,dim,1);
      imgzy.resize(dz,dy,1,dim,1);
      imgxz.resize(dx,dz,1,dim,1);
      return CImg<T>(imgxy.width+imgzy.width,imgxy.height+imgxz.height,1,dim,0).
        draw_image(imgxy,0,0).
        draw_image(imgzy,imgxy.width,0).
        draw_image(imgxz,0,imgxy.height);
    }

    //! In-place version of the previous function.
    CImg<T>& projections2d(const unsigned int x0, const unsigned int y0, const unsigned int z0,
                           const int dx=-100, const int dy=-100, const int dz=-100) {
      return get_projections2d(x0,y0,z0,dx,dy,dz).transfer_to(*this);
    }

    //! Return the image histogram.
    /**
       The histogram H of an image I is a 1D-function where H(x) is the number of
       occurences of the value x in I.
       \param nblevels = Number of different levels of the computed histogram.
       For classical images, this value is 256 (default value). You should specify more levels
       if you are working with CImg<float> or images with high range of pixel values.
       \param val_min = Minimum value considered for the histogram computation. All pixel values lower than val_min
       won't be counted.
       \param val_max = Maximum value considered for the histogram computation. All pixel values higher than val_max
       won't be counted.
       \note If val_min==val_max==0 (default values), the function first estimates the minimum and maximum
       pixel values of the current image, then uses these values for the histogram computation.
       \result The histogram is returned as a 1D CImg<float> image H, having a size of (nblevels,1,1,1) such that
       H(0) and H(nblevels-1) are respectively equal to the number of occurences of the values val_min and val_max in I.
       \note Histogram computation always returns a 1D function. Histogram of multi-valued (such as color) images
       are not multi-dimensional.
       \see get_equalize_histogram(), equalize_histogram()
    **/
    CImg<typename cimg::last<T,float>::type> get_histogram(const unsigned int nblevels=256, const T val_min=(T)0, const T val_max=(T)0) const {
      typedef typename cimg::last<T,float>::type ftype;
      if (is_empty()) return CImg<ftype>();
      if (!nblevels)
        throw CImgArgumentException("CImg<%s>::get_histogram() : Can't compute an histogram with 0 levels",
                                    pixel_type());
      T vmin = val_min, vmax = val_max;
      CImg<ftype> res(nblevels,1,1,1,0);
      if (vmin>=vmax && vmin==0) vmin = minmax(vmax);
      if (vmin<vmax) cimg_for(*this,ptr,T) {
        const int pos = (int)((*ptr-vmin)*(nblevels-1)/(vmax-vmin));
        if (pos>=0 && pos<(int)nblevels) ++res[pos];
      } else res[0]+=size();
      return res;
    }

    //! In-place version of the previous function.
    CImg<T>& histogram(const unsigned int nblevels=256, const T val_min=(T)0, const T val_max=(T)0) {
      return get_histogram(nblevels,val_min,val_max).transfer_to(*this);
    }

    //! Return the histogram-equalized version of the current image.
    /**
       The histogram equalization is a classical image processing algorithm that enhances the image contrast
       by expanding its histogram.
       \param nblevels = Number of different levels of the computed histogram.
       For classical images, this value is 256 (default value). You should specify more levels
       if you are working with CImg<float> or images with high range of pixel values.
       \param val_min = Minimum value considered for the histogram computation. All pixel values lower than val_min
       won't be changed.
       \param val_max = Maximum value considered for the histogram computation. All pixel values higher than val_max
       won't be changed.
       \note If val_min==val_max==0 (default values), the function acts on all pixel values of the image.
       \return A new image with same size is returned, where pixels have been equalized.
       \see get_histogram(), equalize_histogram()
    **/
    CImg<T> get_equalize_histogram(const unsigned int nblevels=256, const T val_min=(T)0, const T val_max=(T)0) const {
      return (+*this).equalize_histogram(nblevels,val_min,val_max);
    }

    //! In-place version of the previous function.
    CImg<T>& equalize_histogram(const unsigned int nblevels=256, const T val_min=(T)0, const T val_max=(T)0) {
      if (!is_empty()) {
        T vmin = val_min, vmax = val_max;
        if (vmin==vmax && vmin==0) vmin = minmax(vmax);
        if (vmin<vmax) {
          CImg<float> hist = get_histogram(nblevels,vmin,vmax);
          float cumul = 0;
          cimg_forX(hist,pos) { cumul+=hist[pos]; hist[pos]=cumul; }
          cimg_for(*this,ptr,T) {
            const int pos = (unsigned int)((*ptr-vmin)*(nblevels-1)/(vmax-vmin));
            if (pos>=0 && pos<(int)nblevels) *ptr = (T)(vmin + (vmax-vmin)*hist[pos]/size());
          }
        }
      }
      return *this;
    }

    //! Get a label map of disconnected regions with same intensities.
    CImg<typename cimg::last<T,unsigned int>::type> get_label_regions() const {

#define _cimg_get_label_test(p,q) { \
  flag = true; \
  const T *ptr1 = ptr(x,y) + siz, *ptr2 = ptr(p,q) + siz; \
  for (unsigned int i = dim; flag && i; --i) { ptr1-=wh; ptr2-=wh; flag = (*ptr1==*ptr2); } \
}

      if (depth>1)
        throw CImgInstanceException("CImg<%s>::label_regions() : Instance image must be a 2D image");
      typedef typename cimg::last<T,unsigned int>::type uitype;
      CImg<uitype> res(width,height,depth,1,0);
      unsigned int label = 1;
      const unsigned int wh = width*height, siz = width*height*dim;
      const int W1 = dimx()-1, H1 = dimy()-1;
      bool flag;
      cimg_forXY(*this,x,y) {
        bool done = false;
        if (y) {
          _cimg_get_label_test(x,y-1);
          if (flag) {
            const unsigned int lab = (res(x,y) = res(x,y-1));
            done = true;
            if (x && res(x-1,y)!=lab) {
              _cimg_get_label_test(x-1,y);
              if (flag) {
                const unsigned int lold = res(x-1,y), *const cptr = res.ptr(x,y);
                for (unsigned int *ptr = res.ptr(); ptr<cptr; ++ptr) if (*ptr==lold) *ptr = lab;
              }
            }
          }
        }
        if (x && !done) { _cimg_get_label_test(x-1,y); if (flag) { res(x,y) = res(x-1,y); done = true; }}
        if (!done) res(x,y) = label++;
      }
      for (int y=H1; y>=0; --y) for (int x=W1; x>=0; --x) {
        bool done = false;
        if (y<H1) {
          _cimg_get_label_test(x,y+1);
          if (flag) {
            const unsigned int lab = (res(x,y) = res(x,y+1));
            done = true;
            if (x<W1 && res(x+1,y)!=lab) {
              _cimg_get_label_test(x+1,y);
              if (flag) {
                const unsigned int lold = res(x+1,y), *const cptr = res.ptr(x,y);
                for (unsigned int *ptr = res.ptr()+res.size()-1; ptr>cptr; --ptr) if (*ptr==lold) *ptr = lab;
              }
            }
          }
        }
        if (x<W1 && !done) { _cimg_get_label_test(x+1,y); if (flag) res(x,y) = res(x+1,y); done = true; }
      }
      const unsigned int lab0 = res.max()+1;
      label = lab0;
      cimg_foroff(res,off) { // Relabel regions
        const unsigned int lab = res[off];
        if (lab<lab0) { cimg_for(res,ptr,unsigned int) if (*ptr==lab) *ptr = label; ++label; }
      }
      return (res-=lab0);
    }

    //! In-place version of the previous function.
    CImg<T>& label_regions() {
      return get_label_regions().transfer_to(*this);
    }

    //! Return the scalar image of vector norms.
    /**
       When dealing with vector-valued images (i.e images with dimv()>1), this function computes the L1,L2 or Linf norm of each
       vector-valued pixel.
       \param norm_type = Type of the norm being computed (1 = L1, 2 = L2, -1 = Linf).
       \return A scalar-valued image CImg<float> with size (dimx(),dimy(),dimz(),1), where each pixel is the norm
       of the corresponding pixels in the original vector-valued image.
       \see get_orientation_pointwise, orientation_pointwise, norm_pointwise.
    **/
    CImg<typename cimg::superset<T,float>::type> get_norm_pointwise(int norm_type=2) const {
      typedef typename cimg::superset<T,float>::type restype;
      if (is_empty()) return CImg<restype>();
      CImg<restype> res(width,height,depth);
      switch(norm_type) {
      case -1: {             // Linf norm
        cimg_forXYZ(*this,x,y,z) {
          restype n = 0; cimg_forV(*this,v) {
            const restype tmp = (restype)cimg::abs((*this)(x,y,z,v));
            if (tmp>n) n=tmp; res(x,y,z) = n;
          }
        }
      } break;
      case 1: {              // L1 norm
        cimg_forXYZ(*this,x,y,z) {
          restype n = 0; cimg_forV(*this,v) n+=cimg::abs((*this)(x,y,z,v)); res(x,y,z) = n;
        }
      } break;
      default: {             // L2 norm
        cimg_forXYZ(*this,x,y,z) {
          restype n = 0; cimg_forV(*this,v) n+=(*this)(x,y,z,v)*(*this)(x,y,z,v); res(x,y,z) = (restype)std::sqrt((double)n);
        }
      } break;
      }
      return res;
    }

    //! In-place version of the previous function.
    CImg<T>& norm_pointwise(int norm_type=2) {
      return get_norm_pointwise(norm_type).transfer_to(*this);
    }

    //! Return the image of normalized vectors
    /**
       When dealing with vector-valued images (i.e images with dimv()>1), this function return the image of normalized vectors
       (unit vectors). Null vectors are unchanged. The L2-norm is computed for the normalization.
       \return A new vector-valued image with same size, where each vector-valued pixels have been normalized.
       \see get_norm_pointwise, norm_pointwise, orientation_pointwise.
    **/
    CImg<typename cimg::superset<T,float>::type> get_orientation_pointwise() const {
      typedef typename cimg::superset<T,float>::type restype;
      if (is_empty()) return CImg<restype>();
      return CImg<restype>(*this,false).orientation_pointwise();
    }

    //! In-place version of the previous function.
    CImg<T>& orientation_pointwise() {
      cimg_forXYZ(*this,x,y,z) {
        float n = 0.0f;
        cimg_forV(*this,v) n+=(float)((*this)(x,y,z,v)*(*this)(x,y,z,v));
        n = (float)std::sqrt(n);
        if (n>0) cimg_forV(*this,v) (*this)(x,y,z,v) = (T)((*this)(x,y,z,v)/n);
        else cimg_forV(*this,v) (*this)(x,y,z,v) = 0;
      }
      return *this;
    }

    //! Split image into a list.
    CImgList<T> get_split(const char axe='x', const unsigned int nb=0) const {
      if (is_empty()) return CImgList<T>();
      CImgList<T> res;
      switch (cimg::uncase(axe)) {
      case 'x': {
        if (nb>width)
          throw CImgArgumentException("CImg<%s>::get_split() : Cannot split instance image (%u,%u,%u,%u,%p) along 'x' into %u images.",
                                      pixel_type(),width,height,depth,dim,data,nb);
        res.assign(nb?nb:width);
        const unsigned int delta = width/res.size + ((width%res.size)?1:0);
        unsigned int l,x;
        for (l=0, x=0; l<res.size-1; ++l, x+=delta) res[l] = get_crop(x,0,0,0,x+delta-1,height-1,depth-1,dim-1);
        res[res.size-1] = get_crop(x,0,0,0,width-1,height-1,depth-1,dim-1);
      } break;
      case 'y': {
        if (nb>height)
          throw CImgArgumentException("CImg<%s>::get_split() : Cannot split instance image (%u,%u,%u,%u,%p) along 'y' into %u images.",
                                      pixel_type(),width,height,depth,dim,data,nb);
        res.assign(nb?nb:height);
        const unsigned int delta = height/res.size + ((height%res.size)?1:0);
        unsigned int l,x;
        for (l=0, x=0; l<res.size-1; ++l, x+=delta) res[l] = get_crop(0,x,0,0,width-1,x+delta-1,depth-1,dim-1);
        res[res.size-1] = get_crop(0,x,0,0,width-1,height-1,depth-1,dim-1);
      } break;
      case 'z': {
        if (nb>depth)
          throw CImgArgumentException("CImg<%s>::get_split() : Cannot split instance image (%u,%u,%u,%u,%p) along 'z' into %u images.",
                                      pixel_type(),width,height,depth,dim,data,nb);
        res.assign(nb?nb:depth);
        const unsigned int delta = depth/res.size + ((depth%res.size)?1:0);
        unsigned int l,x;
        for (l=0, x=0; l<res.size-1; ++l, x+=delta) res[l] = get_crop(0,0,x,0,width-1,height-1,x+delta-1,dim-1);
        res[res.size-1] = get_crop(0,0,x,0,width-1,height-1,depth-1,dim-1);
      } break;
      case 'v': {
        if (nb>dim)
          throw CImgArgumentException("CImg<%s>::get_split() : Cannot split instance image (%u,%u,%u,%u,%p) along 'v' into %u images.",
                                      pixel_type(),width,height,depth,dim,data,nb);
        res.assign(nb?nb:dim);
        const unsigned int delta = dim/res.size + ((dim%res.size)?1:0);
        unsigned int l,x;
        for (l=0, x=0; l<res.size-1; ++l, x+=delta) res[l] = get_crop(0,0,0,x,width-1,height-1,depth-1,x+delta-1);
        res[res.size-1] = get_crop(0,0,0,x,width-1,height-1,depth-1,dim-1);
      } break;
      default:
        throw CImgArgumentException("CImg<%s>::get_split() : Unknown axe '%c', must be 'x','y','z' or 'v'",pixel_type(),axe);
        break;
      }
      return res;
    }

    //! Append an image to another one
    CImg<T> get_append(const CImg<T>& img, const char axis='x', const char align='c') const {
      if (!img) return *this;
      if (is_empty()) return img;
      CImgList<T> temp(2);
      temp[0].width = width; temp[0].height = height; temp[0].depth = depth;
      temp[0].dim = dim; temp[0].data = data;
      temp[1].width = img.width; temp[1].height = img.height; temp[1].depth = img.depth;
      temp[1].dim = img.dim; temp[1].data = img.data;
      const CImg<T> res = temp.get_append(axis,align);
      temp[0].width = temp[0].height = temp[0].depth = temp[0].dim = 0; temp[0].data = 0;
      temp[1].width = temp[1].height = temp[1].depth = temp[1].dim = 0; temp[1].data = 0;
      return res;
    }

    //! In-place version of the previous function.
    CImg<T>& append(const CImg<T>& img, const char axis='x', const char align='c') {
      if (!img) return *this;
      if (is_empty()) return (*this=img);
      return get_append(img,axis,align).transfer_to(*this);
    }

    //! Return a 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<typename cimg::superset<T,float>::type> get_gradientXY(const int scheme=0) const {
      typedef typename cimg::superset<T,float>::type restype;
      if (is_empty()) return CImgList<restype>(2);
      CImgList<restype> res(2,width,height,depth,dim);
      switch(scheme) {
      case -1: { // backward finite differences
        CImg_3x3(I,restype);
        cimg_forZV(*this,z,k) cimg_for3x3(*this,x,y,z,k,I) { res[0](x,y,z,k) = Icc-Ipc; res[1](x,y,z,k) = Icc-Icp; }
      } break;
      case 1: { // forward finite differences
        CImg_2x2(I,restype);
        cimg_forZV(*this,z,k) cimg_for2x2(*this,x,y,z,k,I) { res[0](x,y,0,k) = Inc-Icc; res[1](x,y,z,k) = Icn-Icc; }
      } break;
      case 2: { // using Sobel mask
        CImg_3x3(I,restype);
        const float a = 1, b = 2;
        cimg_forZV(*this,z,k) cimg_for3x3(*this,x,y,z,k,I) {
          res[0](x,y,z,k) = -a*Ipp-b*Ipc-a*Ipn+a*Inp+b*Inc+a*Inn;
          res[1](x,y,z,k) = -a*Ipp-b*Icp-a*Inp+a*Ipn+b*Icn+a*Inn;
        }
      } break;
      case 3: { // using rotation invariant mask
        CImg_3x3(I,restype);
        const float a = (float)(0.25*(2-std::sqrt(2.0))), b = (float)(0.5f*(std::sqrt(2.0)-1));
        cimg_forZV(*this,z,k) cimg_for3x3(*this,x,y,z,k,I) {
          res[0](x,y,z,k) = -a*Ipp-b*Ipc-a*Ipn+a*Inp+b*Inc+a*Inn;
          res[1](x,y,z,k) = -a*Ipp-b*Icp-a*Inp+a*Ipn+b*Icn+a*Inn;
        }
      } break;
      case 4: { // using Deriche filter with low standard variation
        res[0] = get_deriche(0,1,'x');
        res[1] = get_deriche(0,1,'y');
      } break;
      default: { // central finite differences
        CImg_3x3(I,restype);
        cimg_forZV(*this,z,k) cimg_for3x3(*this,x,y,z,k,I) {
          res[0](x,y,z,k) = 0.5f*(Inc-Ipc);
          res[1](x,y,z,k) = 0.5f*(Icn-Icp);
        }
      } break;
      }
      return res;
    }

    //! Return a list of images, corresponding to the XYZ-gradients of an image.
    /**
       \see get_gradientXY().
    **/
    CImgList<typename cimg::superset<T,float>::type> get_gradientXYZ(const int scheme=0) const {
      typedef typename cimg::superset<T,float>::type restype;
      if (is_empty()) return CImgList<restype>(3);
      CImgList<restype> res(3,width,height,depth,dim);
      CImg_3x3x3(I,restype);
      switch(scheme) {
      case -1: { // backward finite differences
        cimg_forV(*this,k) cimg_for3x3x3(*this,x,y,z,k,I) {
          res[0](x,y,z,k) = Iccc-Ipcc;
          res[1](x,y,z,k) = Iccc-Icpc;
          res[2](x,y,z,k) = Iccc-Iccp;
        }
      } break;
      case 1: { // forward finite differences
        cimg_forV(*this,k) cimg_for3x3x3(*this,x,y,z,k,I) {
          res[0](x,y,z,k) = Incc-Iccc;
          res[1](x,y,z,k) = Icnc-Iccc;
          res[2](x,y,z,k) = Iccn-Iccc;
        }
      } break;
      case 4: { // using Deriche filter with low standard variation
        res[0] = get_deriche(0,1,'x');
        res[1] = get_deriche(0,1,'y');
        res[2] = get_deriche(0,1,'z');
      } break;
      default: { // central finite differences
        cimg_forV(*this,k) cimg_for3x3x3(*this,x,y,z,k,I) {
          res[0](x,y,z,k) = 0.5f*(Incc-Ipcc);
          res[1](x,y,z,k) = 0.5f*(Icnc-Icpc);
          res[2](x,y,z,k) = 0.5f*(Iccn-Iccp);
        }
      } break;
      }
      return res;
    }

    //! Return the 2D structure tensor field of an image
    CImg<typename cimg::superset<T,float>::type> get_structure_tensorXY(const int scheme=1) const {
      typedef typename cimg::superset<T,float>::type restype;
      if (is_empty()) return CImg<restype>();
      CImg<restype> res(width,height,depth,3,0);
      CImg_3x3(I,restype);
      switch (scheme) {
      case 0: { // classical central finite differences
        cimg_forZV(*this,z,k) cimg_for3x3(*this,x,y,0,k,I) {
          const restype
            ix = 0.5f*(Inc-Ipc),
            iy = 0.5f*(Icn-Icp);
          res(x,y,z,0)+=ix*ix;
          res(x,y,z,1)+=ix*iy;
          res(x,y,z,2)+=iy*iy;
        }
      } break;
      default: { // Precise forward/backward finite differences
        cimg_forZV(*this,z,k) cimg_for3x3(*this,x,y,0,k,I) {
          const restype
            ixf = Inc-Icc, ixb = Icc-Ipc,
            iyf = Icn-Icc, iyb = Icc-Icp;
          res(x,y,z,0) += 0.5f*(ixf*ixf+ixb*ixb);
          res(x,y,z,1) += 0.25f*(ixf*iyf+ixf*iyb+ixb*iyf+ixb*iyb);
          res(x,y,z,2) += 0.5f*(iyf*iyf+iyb*iyb);
        }
      } break;
      }
      return res;
    }

    //! In-place version of the previous function.
    CImg<T>& structure_tensorXY(const int scheme=1) {
      return get_structure_tensorXY(scheme).transfer_to(*this);
    }

    //! Return the 3D structure tensor field of an image
    CImg<typename cimg::superset<T,float>::type> get_structure_tensorXYZ(const int scheme=1) const {
      typedef typename cimg::superset<T,float>::type restype;
      if (is_empty()) return CImg<restype>();
      CImg<restype> res(width,height,depth,6,0);
      CImg_3x3x3(I,restype);
      switch (scheme) {
      case 0: { // classical central finite differences
        cimg_forV(*this,k) cimg_for3x3x3(*this,x,y,z,k,I) {
          const restype
            ix = 0.5f*(Incc-Ipcc),
            iy = 0.5f*(Icnc-Icpc),
            iz = 0.5f*(Iccn-Iccp);
          res(x,y,z,0)+=ix*ix;
          res(x,y,z,1)+=ix*iy;
          res(x,y,z,2)+=ix*iz;
          res(x,y,z,3)+=iy*iy;
          res(x,y,z,4)+=iy*iz;
          res(x,y,z,5)+=iz*iz;
        }
      } break;
      default: { // Precise forward/backward finite differences
        cimg_forV(*this,k) cimg_for3x3x3(*this,x,y,z,k,I) {
          const restype
            ixf = Incc-Iccc, ixb = Iccc-Ipcc,
            iyf = Icnc-Iccc, iyb = Iccc-Icpc,
            izf = Iccn-Iccc, izb = Iccc-Iccp;
          res(x,y,z,0) += 0.5f*(ixf*ixf + ixb*ixb);
          res(x,y,z,1) += 0.25f*(ixf*iyf + ixf*iyb + ixb*iyf + ixb*iyb);
          res(x,y,z,2) += 0.25f*(ixf*izf + ixf*izb + ixb*izf + ixb*izb);
          res(x,y,z,3) += 0.5f*(iyf*iyf + iyb*iyb);
          res(x,y,z,4) += 0.25f*(iyf*izf + iyf*izb + iyb*izf + iyb*izb);
          res(x,y,z,5) += 0.5f*(izf*izf + izb*izb);
        }
      } break;
      }
      return res;
    }

    //! In-place version of the previous function.
    CImg<T>& structure_tensorXYZ(const int scheme=1) {
      return get_structure_tensorXYZ(scheme).transfer_to(*this);
    }

    //! Get components of the 2D Hessian matrix of an image.
    /**
       Components are ordered as : Ixx, Ixy, Iyy
    **/
    CImgList<typename cimg::superset<T,float>::type> get_hessianXY() {
      typedef typename cimg::superset<T,float>::type ftype;
      if (is_empty()) return CImgList<ftype>(3);
      CImgList<ftype> res(3,width,height,depth,dim);
      CImg_3x3(I,ftype);
      cimg_forZV(*this,z,k) cimg_for3x3(*this,x,y,z,k,I) {
        res[0](x,y,z,k) = Ipc + Inc - 2*Icc;             // Ixx
        res[1](x,y,z,k) = 0.25f*(Ipp + Inn - Ipn - Inp); // Ixy
        res[2](x,y,z,k) = Icp + Icn - 2*Icc;             // Iyy
      }
      return res;
    }

    //! Get components of the 3D Hessian matrix of an image.
    /**
       Components are ordered as : Ixx, Ixy, Ixz, Iyy, Iyz, Izz.
    **/
    CImgList<typename cimg::superset<T,float>::type> get_hessianXYZ() {
      typedef typename cimg::superset<T,float>::type ftype;
      if (is_empty()) return CImgList<ftype>(6);
      CImgList<ftype> res(6,width,height,depth,dim);
      CImg_3x3x3(I,ftype);
      cimg_forV(*this,k) cimg_for3x3x3(*this,x,y,z,k,I) {
        res[0](x,y,z,k) = Ipcc + Incc - 2*Iccc;              // Ixx
        res[1](x,y,z,k) = 0.25f*(Ippc + Innc - Ipnc - Inpc); // Ixy
        res[2](x,y,z,k) = 0.25f*(Ipcp + Incn - Ipcn - Incp); // Ixz
        res[3](x,y,z,k) = Icpc + Icnc - 2*Iccc;              // Iyy
        res[4](x,y,z,k) = 0.25f*(Icpp + Icnn - Icpn - Icnp); // Iyz
        res[5](x,y,z,k) = Iccn + Iccp - 2*Iccc;              // Izz
      }
      return res;
    }

    //! Get distance function from 0-valued isophotes by the application of the eikonal equation.
    CImg<typename cimg::superset<T,float>::type> get_distance_function(const unsigned int nb_iter=100, const float band_size=0.0f, const float precision=0.5f) const {
      typedef typename cimg::superset<T,float>::type restype;
      return CImg<restype>(*this,false).distance_function(nb_iter,band_size,precision);
    }

    //! In-place version of the previous function.
    CImg<T>& distance_function(const unsigned int nb_iter=100, const float band_size=0.0f, const float precision=0.5f) {
      typedef typename cimg::superset<T,float>::type ftype;
      if (is_empty()) return *this;
      CImg<ftype> veloc(*this);
      for (unsigned int iter=0; iter<nb_iter; ++iter) {

        if (depth>1) { // 3D version
          CImg_3x3x3(I,ftype);
          cimg_forV(*this,k) cimg_for3x3x3(*this,x,y,z,k,I) if (band_size<=0 || cimg::abs(Iccc)<band_size) {
            const ftype
              gx  = 0.5f*(Incc-Ipcc),
              gy  = 0.5f*(Icnc-Icpc),
              gz  = 0.5f*(Iccn-Iccp),
              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  = 1e-5f+(ftype)std::sqrt(gx*gx+gy*gy+gz*gz),
              ngx = gx/ng,
              ngy = gy/ng,
              ngz = gz/ng;
            veloc(x,y,z,k) = sgn*(ngx*ix+ngy*iy+ngz*iz-1);
          }
        } else { // 2D version
          CImg_3x3(I,ftype);
          cimg_forV(*this,k) cimg_for3x3(*this,x,y,0,k,I) if (band_size<=0 || cimg::abs(Icc)<band_size) {
            const ftype
              gx  = 0.5f*(Inc-Ipc),
              gy  = 0.5f*(Icn-Icp),
              sgn = -cimg::sign(Icc),
              ix  = gx*sgn>0?Inc-Icc:Icc-Ipc,
              iy  = gy*sgn>0?Icn-Icc:Icc-Icp,
              ng  = 1e-5f+(ftype)std::sqrt(gx*gx+gy*gy),
              ngx = gx/ng,
              ngy = gy/ng;
            veloc(x,y,k) = sgn*(ngx*ix+ngy*iy-1);
          }
        }
        float m, M = (float)veloc.maxmin(m), xdt = precision/(float)cimg::max(cimg::abs(m),cimg::abs(M));
        *this+=(veloc*=xdt);
      }
      return *this;
    }

    //! Return 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> get_dijkstra(const tf& distance, const unsigned int nb_nodes,
                               const unsigned int starting_node, const unsigned int ending_node,
                               CImg<t>& previous) {

      CImg<T> dist(1,nb_nodes,1,1,cimg::type<T>::max());
      dist(starting_node) = 0;
      previous.assign(1,nb_nodes,1,1,(t)-1);
      previous(starting_node) = (t)starting_node;
      typedef typename cimg::last<T,unsigned int>::type uitype;
      CImg<uitype> 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;
    }

    template<typename tf, typename t>
      static CImg<T> get_dijkstra(const tf& distance, const unsigned int nb_nodes,
                               const unsigned int starting_node, const unsigned int ending_node=~0U) {
      typedef typename cimg::last<T,unsigned int>::type uitype;
      CImg<uitype> foo;
      return get_dijkstra(distance,nb_nodes,starting_node,ending_node,foo);
    }

    //! Return minimal path in a graph, using the Dijkstra algorithm.
    /**
       Instance image corresponds to the adjacency matrix of the graph.
       \param starting_node Indice of the starting node.
       \param previous Array that gives the previous node indice in the path to the starting node (optional parameter).
       \return Array of distances of each node to the starting node.
    **/
    template<typename t> CImg<T> get_dijkstra(const unsigned int starting_node, const unsigned int ending_node, CImg<t>& previous) const {
      if (width!=height || depth!=1 || dim!=1)
        throw CImgInstanceException("CImg<%s>::dijkstra() : Instance image (%u,%u,%u,%u,%p) is not a graph adjacency matrix",
                                    pixel_type(),width,height,depth,dim,data);
      return CImg<T>::get_dijkstra(*this,width,starting_node,ending_node,previous);
    }

    // In-place version of the previous function
    template<typename t> CImg<T>& dijkstra(const unsigned int starting_node, const unsigned int ending_node, CImg<t>& previous) {
      return get_dijkstra(starting_node,ending_node,previous).transfer_to(*this);
    }

    //! Return minimal path in a graph, using the Dijkstra algorithm
    CImg<typename cimg::superset<T,float>::type> get_dijkstra(const unsigned int starting_node, const unsigned int ending_node=~0U) const {
      typedef typename cimg::last<T,unsigned int>::type uitype;
      CImg<uitype> foo;
      return get_dijkstra(starting_node,ending_node,foo);
    }

    // In-place version of the previous function
    CImg<T>& dijkstra(const unsigned int starting_node, const unsigned int ending_node=~0U) {
      return get_dijkstra(starting_node,ending_node).transfer_to(*this);
    }

    //@}
    //-------------------------------------
    //
    //! \name Meshes and Triangulations
    //@{
    //-------------------------------------

    struct _marching_squares_func {
      const CImg<T>& ref;
      _marching_squares_func(const CImg<T>& pref):ref(pref) {}
      float operator()(const float x, const float y) const {
        return (float)ref((int)x,(int)y);
      }
    };

    struct _marching_cubes_func {
      const CImg<T>& ref;
      _marching_cubes_func(const CImg<T>& pref):ref(pref) {}
      float operator()(const float x, const float y, const float z) const {
        return (float)ref((int)x,(int)y,(int)z);
      }
    };

    struct _marching_squares_func_float {
      const CImg<T>& ref;
      _marching_squares_func_float(const CImg<T>& pref):ref(pref) {}
      float operator()(const float x, const float y) const {
        return (float)ref.linear_pix2d(x,y);
      }
    };

    struct _marching_cubes_func_float {
      const CImg<T>& ref;
      _marching_cubes_func_float(const CImg<T>& pref):ref(pref) {}
      float operator()(const float x, const float y, const float z) const {
        return (float)ref.linear_pix3d(x,y,z);
      }
    };

    //! Get a vectorization of an implicit function defined by the instance image.
    template<typename tp, typename tf>
    const CImg<T>& marching_squares(const float isovalue, CImgList<tp>& points, CImgList<tf>& primitives) const {
      if (height<=1 || depth>1 || dim>1)
        throw CImgInstanceException("CImg<%s>::marching_squares() : Instance image (%u,%u,%u,%u,%p) is not a 2D scalar image.",
                                    pixel_type(),width,height,depth,dim,data);
      const _marching_squares_func func(*this);
      cimg::marching_squares(func,isovalue,0.0f,0.0f,dimx()-1.0f,dimy()-1.0f,1.0f,1.0f,points,primitives);
      return *this;
    }

    //! Get a vectorization of an implicit function defined by the instance image.
    /**
       This version allows to specify the marching squares resolution along x,y, and z.
    **/
    template<typename tp, typename tf>
    const CImg<T>& marching_squares(const float isovalue,
                                    const float resx, const float resy,
                                    CImgList<tp>& points, CImgList<tf>& primitives) const {
      if (height<=1 || depth>1 || dim>1)
        throw CImgInstanceException("CImg<%s>::marching_squares() : Instance image (%u,%u,%u,%u,%p) is not a 2D scalar image.",
                                    pixel_type(),width,height,depth,dim,data);
      const _marching_squares_func_float func(*this);
      cimg::marching_squares(func,isovalue,0.0f,0.0f,dimx()-1.0f,dimy()-1.0f,resx,resy,points,primitives);
      return *this;
    }

    //! Get a triangulation of an implicit function defined by the instance image
    template<typename tp, typename tf>
    const CImg<T>& marching_cubes(const float isovalue, CImgList<tp>& points, CImgList<tf>& primitives,
                                  const bool invert_faces = false) const {
      if (depth<=1 || dim>1)
        throw CImgInstanceException("CImg<%s>::marching_cubes() : Instance image (%u,%u,%u,%u,%p) is not a 3D scalar image.",
                                    pixel_type(),width,height,depth,dim,data);
      const _marching_cubes_func func(*this);
      cimg::marching_cubes(func,isovalue,0.0f,0.0f,0.0f,dimx()-1.0f,dimy()-1.0f,dimz()-1.0f,
                           1.0f,1.0f,1.0f,points,primitives,invert_faces);
      return *this;
    }

    //! Get a triangulation of an implicit function defined by the instance image
    /**
       This version allows to specify the marching cube resolution along x,y and z.
    **/
    template<typename tp, typename tf>
    const CImg<T>& marching_cubes(const float isovalue,
                                  const float resx, const float resy, const float resz,
                                  CImgList<tp>& points, CImgList<tf>& primitives,
                                  const bool invert_faces = false) const {
      if (depth<=1 || dim>1)
        throw CImgInstanceException("CImg<%s>::marching_cubes() : Instance image (%u,%u,%u,%u,%p) is not a 3D scalar image.",
                                    pixel_type(),width,height,depth,dim,data);
      const _marching_cubes_func_float func(*this);
      cimg::marching_cubes(func,isovalue,0.0f,0.0f,0.0f,dimx()-1.0f,dimy()-1.0f,dimz()-1.0f,
                           resx,resy,resz,points,primitives,invert_faces);
      return *this;
    }

    //@}
    //----------------------------
    //
    //! \name Color conversions
    //@{
    //----------------------------

    //! Return the default 256 colors palette.
    /**
       The default color palette is used by %CImg when displaying images on 256 colors displays.
       It consists in the quantification of the (R,G,B) color space using 3:3:2 bits for color coding
       (i.e 8 levels for the Red and Green and 4 levels for the Blue).
       \return A 256x1x1x3 color image defining the palette entries.
    **/
    static CImg<T> get_default_LUT8() {
      static CImg<T> 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) = (T)r;
              palette(0,index,1) = (T)g;
              palette(0,index++,2) = (T)b;
            }
      }
      return palette;
    }

    //! Return a rainbow-palette
    static CImg<T> get_rainbow_LUT8() {
      static CImg<T> palette;
      if (!palette) {
        typedef typename cimg::superset<T,int>::type itype;
        CImg<itype> tmp(1,256,1,3,255);
        tmp.get_shared_channel(0).sequence(0,359);
        tmp.HSVtoRGB();
        palette = tmp;
      }
      return palette;
    }

    //! Return contrasted palette optmized for cluster visualization
    static CImg<T> get_cluster_LUT8() {
      static const unsigned char pal[] =
        { 217,62,88,75,1,237,240,12,56,160,165,116,1,1,204,2,15,248,148,185,133,141,46,246,222,116,16,5,207,226,
          17,114,247,1,214,53,238,0,95,55,233,235,109,0,17,54,33,0,90,30,3,0,94,27,19,0,68,212,166,130,0,15,7,119,
          238,2,246,198,0,3,16,10,13,2,25,28,12,6,2,99,18,141,30,4,3,140,12,4,30,233,7,10,0,136,35,160,168,184,20,
          233,0,1,242,83,90,56,180,44,41,0,6,19,207,5,31,214,4,35,153,180,75,21,76,16,202,218,22,17,2,136,71,74,
          81,251,244,148,222,17,0,234,24,0,200,16,239,15,225,102,230,186,58,230,110,12,0,7,129,249,22,241,37,219,
          1,3,254,210,3,212,113,131,197,162,123,252,90,96,209,60,0,17,0,180,249,12,112,165,43,27,229,77,40,195,12,
          87,1,210,148,47,80,5,9,1,137,2,40,57,205,244,40,8,252,98,0,40,43,206,31,187,0,180,1,69,70,227,131,108,0,
          223,94,228,35,248,243,4,16,0,34,24,2,9,35,73,91,12,199,51,1,249,12,103,131,20,224,2,70,32,
          233,1,165,3,8,154,246,233,196,5,0,6,183,227,247,195,208,36,0,0,226,160,210,198,69,153,210,1,23,8,192,2,4,
          137,1,0,52,2,249,241,129,0,0,234,7,238,71,7,32,15,157,157,252,158,2,250,6,13,30,11,162,0,199,21,11,27,224,
          4,157,20,181,111,187,218,3,0,11,158,230,196,34,223,22,248,135,254,210,157,219,0,117,239,3,255,4,227,5,247,
          11,4,3,188,111,11,105,195,2,0,14,1,21,219,192,0,183,191,113,241,1,12,17,248,0,48,7,19,1,254,212,0,239,246,
          0,23,0,250,165,194,194,17,3,253,0,24,6,0,141,167,221,24,212,2,235,243,0,0,205,1,251,133,204,28,4,6,1,10,
          141,21,74,12,236,254,228,19,1,0,214,1,186,13,13,6,13,16,27,209,6,216,11,207,251,59,32,9,155,23,19,235,143,
          116,6,213,6,75,159,23,6,0,228,4,10,245,249,1,7,44,234,4,102,174,0,19,239,103,16,15,18,8,214,22,4,47,244,
          255,8,0,251,173,1,212,252,250,251,252,6,0,29,29,222,233,246,5,149,0,182,180,13,151,0,203,183,0,35,149,0,
          235,246,254,78,9,17,203,73,11,195,0,3,5,44,0,0,237,5,106,6,130,16,214,20,168,247,168,4,207,11,5,1,232,251,
          129,210,116,231,217,223,214,27,45,38,4,177,186,249,7,215,172,16,214,27,249,230,236,2,34,216,217,0,175,30,
          243,225,244,182,20,212,2,226,21,255,20,0,2,13,62,13,191,14,76,64,20,121,4,118,0,216,1,147,0,2,210,1,215,
          95,210,236,225,184,46,0,248,24,11,1,9,141,250,243,9,221,233,160,11,147,2,55,8,23,12,253,9,0,54,0,231,6,3,
          141,8,2,246,9,180,5,11,8,227,8,43,110,242,1,130,5,97,36,10,6,219,86,133,11,108,6,1,5,244,67,19,28,0,174,
          154,16,127,149,252,188,196,196,228,244,9,249,0,0,0,37,170,32,250,0,73,255,23,3,224,234,38,195,198,0,255,87,
          33,221,174,31,3,0,189,228,6,153,14,144,14,108,197,0,9,206,245,254,3,16,253,178,248,0,95,125,8,0,3,168,21,
          23,168,19,50,240,244,185,0,1,144,10,168,31,82,1,13 };
      static const CImg<T> palette(pal,256,1,1,3);
      return palette;
    }

    //! Convert color pixels from (R,G,B) to match a specified palette.
    /**
       This function return a (R,G,B) image where colored pixels are constrained to match entries
       of the specified color \c palette.
       \param palette User-defined palette that will constraint the color conversion.
       \param dithering Enable/Disable Floyd-Steinberg dithering.
       \param indexing If \c true, each resulting image pixel is an index to the given color palette.
       Otherwise, (R,G,B) values of the palette are copied instead.
    **/
    template<typename t> CImg<t> get_RGBtoLUT(const CImg<t>& palette, const bool dithering=true, const bool indexing=false) const {
      if (is_empty()) return CImg<t>();
      if (dim!=3) throw CImgInstanceException("CImg<%s>::RGBtoLUT() : Input image dimension is dim=%u, "
                                              "should be a (R,G,B) image.",pixel_type(),dim);
      if (palette.data && palette.dim!=3)
        throw CImgArgumentException("CImg<%s>::RGBtoLUT() : Given palette dimension is dim=%u, "
                                    "should be a (R,G,B) palette",pixel_type(),palette.dim);
      CImg<t> res(width,height,depth,indexing?1:3), pal = palette.data?palette:CImg<t>::get_default_LUT8();
      float *line1 = new float[3*width], *line2 = new float[3*width], *pline1 = line1, *pline2 = line2;
      cimg_forZ(*this,z) {
        float *ptr = pline2; cimg_forX(*this,x) { *(ptr++) = (*this)(x,0,z,0); *(ptr++) = (*this)(x,0,z,1); *(ptr++) = (*this)(x,0,z,2); }
        cimg_forY(*this,y) {
          cimg::swap(pline1,pline2);
          if (y<dimy()-1) {
            const int ny = y+1;
            float *ptr = pline2; cimg_forX(*this,x) { *(ptr++) = (*this)(x,ny,z,0); *(ptr++) = (*this)(x,ny,z,1); *(ptr++) = (*this)(x,ny,z,2); }
          }
          float *ptr1=pline1, *ptr2=pline2;
          cimg_forX(*this,x) {
            float R = *(ptr1++), G = *(ptr1++), B = *(ptr1++);
            R = R<0?0:(R>255?255:R); G = G<0?0:(G>255?255:G); B = B<0?0:(B>255?255:B);
            int best_index = 0;
            t Rbest = 0, Gbest = 0, Bbest = 0;
            if (palette.data) { // find best match in given color palette
              float min = cimg::type<float>::max();
              cimg_forX(palette,off) {
                const t Rp = palette(off,0), Gp = palette(off,1), Bp = palette(off,2);
                const float error = (float)((Rp-R)*(Rp-R) + (Gp-G)*(Gp-G) + (Bp-B)*(Bp-B));
                if (error<min) { min=error; best_index=off; Rbest=Rp; Gbest=Gp; Bbest=Bp; }
              }
            } else {
              Rbest = (t)((unsigned char)R&0xe0); Gbest = (t)((unsigned char)G&0xe0); Bbest = (t)((unsigned char)B&0xc0);
              best_index = (unsigned char)Rbest | ((unsigned char)Gbest>>3) | ((unsigned char)Bbest>>6);
            }
            if (indexing) res(x,y,z) = best_index;
            else { res(x,y,z,0) = Rbest; res(x,y,z,1) = Gbest; res(x,y,z,2) = Bbest; }
            if (dithering) { // apply dithering to neighborhood pixels if needed
              const float dR = (float)(R-Rbest), dG = (float)(G-Gbest), dB = (float)(B-Bbest);
              if (x<dimx()-1) { *(ptr1++)+= dR*7/16; *(ptr1++)+= dG*7/16; *(ptr1++)+= dB*7/16; ptr1-=3; }
              if (y<dimy()-1) {
                *(ptr2++)+= dR*5/16; *(ptr2++)+= dG*5/16; *ptr2+= dB*5/16; ptr2-=2;
                if (x>0) { *(--ptr2)+= dB*3/16; *(--ptr2)+= dG*3/16; *(--ptr2)+= dR*3/16; ptr2+=3; }
                if (x<dimx()-1) { ptr2+=3; *(ptr2++)+= dR/16; *(ptr2++)+= dG/16; *ptr2+= dB/16; ptr2-=5; }
              }
            }
            ptr2+=3;
          }
        }
      }
      delete[] line1; delete[] line2;
      return res;
    }

    //! In-place version of the previous function.
    CImg<T>& RGBtoLUT(const CImg<T>& palette, const bool dithering=true, const bool indexing=false) {
      return get_RGBtoLUT(palette,dithering,indexing).transfer_to(*this);
    }

    //! Convert color pixels from (R,G,B) to match the default 256 colors palette.
    /**
       Same as get_RGBtoLUT() with the default color palette given by get_default_LUT8().
    **/
    CImg<T> get_RGBtoLUT(const bool dithering=true, const bool indexing=false) const {
      CImg<T> foo;
      return get_RGBtoLUT(foo,dithering,indexing);
    }

    //! In-place version of the previous function.
    CImg<T>& RGBtoLUT(const bool dithering=true, const bool indexing=false) {
      CImg<T> foo;
      return get_RGBtoLUT(foo,dithering,indexing).transfer_to(*this);
    }

    //! Convert an indexed image to a (R,G,B) image using the specified color palette.
    template<typename t> CImg<t> get_LUTtoRGB(const CImg<t>& palette) const {
      if (is_empty()) return CImg<t>();
      if (dim!=1) throw CImgInstanceException("CImg<%s>::LUTtoRGB() : Input image dimension is dim=%u, "
                                              "should be a LUT image",pixel_type(),dim);
      if (palette.data && palette.dim!=3)
        throw CImgArgumentException("CImg<%s>::LUTtoRGB() : Given palette dimension is dim=%u, "
                                    "should be a (R,G,B) palette",pixel_type(),palette.dim);
      CImg<t> res(width,height,depth,3);
      CImg<t> pal = palette.data?palette:CImg<t>::get_default_LUT8();
      cimg_forXYZ(*this,x,y,z) {
        const unsigned int index = (unsigned int)(*this)(x,y,z);
        res(x,y,z,0) = pal(index,0);
        res(x,y,z,1) = pal(index,1);
        res(x,y,z,2) = pal(index,2);
      }
      return res;
    }

    //! In-place version of the previous function.
    CImg<T>& LUTtoRGB(const CImg<T>& palette) {
      return get_LUTtoRGB(palette).transfer_to(*this);
    }

    //! Convert an indexed image (with the default palette) to a (R,G,B) image.
    CImg<T> get_LUTtoRGB() const {
      CImg<T> foo;
      return get_LUTtoRGB(foo);
    }

    //! In-place version of the previous function.
    CImg<T>& LUTtoRGB() {
      CImg<T> foo;
      return get_LUTtoRGB(foo).transfer_to(*this);
    }

    //! Convert color pixels from (R,G,B) to (H,S,V).
    CImg<typename cimg::superset<T,float>::type> get_RGBtoHSV() const {
      typedef typename cimg::superset<T,float>::type restype;
      return CImg<restype>(*this,false).RGBtoHSV();
    }

    //! In-place version of the previous function.
    CImg<T>& RGBtoHSV() {
      if (!is_empty()) {
        if (dim!=3) throw CImgInstanceException("CImg<%s>::RGBtoHSV() : Input image dimension is dim=%u, "
                                                "should be a (R,G,B) image.",pixel_type(),dim);
        cimg_forXYZ(*this,x,y,z) {
          const float
            R = (float)((*this)(x,y,z,0)),
            G = (float)((*this)(x,y,z,1)),
            B = (float)((*this)(x,y,z,2)),
            nR = (R<0?0:(R>255?255:R))/255.0f,
            nG = (G<0?0:(G>255?255:G))/255.0f,
            nB = (B<0?0:(B>255?255:B))/255.0f,
            m = cimg::min(nR,nG,nB),
            M = cimg::max(nR,nG,nB);
          float H = 0, S = 0;
          if (M!=m) {
            const float
              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.0f) H-=6.0f;
            H*=60;
            S = (M-m)/M;
          }
          (*this)(x,y,z,0) = (T)H;
          (*this)(x,y,z,1) = (T)S;
          (*this)(x,y,z,2) = (T)M;
        }
      }
      return *this;
    }

    //! Convert color pixels from (H,S,V) to (R,G,B).
    CImg<T> get_HSVtoRGB() const {
      return (+*this).HSVtoRGB();
    }

    //! In-place version of the previous function.
    CImg<T>& HSVtoRGB() {
      if (!is_empty()) {
        if (dim!=3) throw CImgInstanceException("CImg<%s>::HSVtoRGB() : Input image dimension is dim=%u, "
                                                "should be a (H,S,V) image",pixel_type(),dim);
        cimg_forXYZ(*this,x,y,z) {
          float
            H = (float)((*this)(x,y,z,0)),
            S = (float)((*this)(x,y,z,1)),
            V = (float)((*this)(x,y,z,2));
          float R = 0, G = 0, B = 0;
          if (H==0 && S==0) R = G = B = V;
          else {
            H/=60.0f;
            const int i = (int)std::floor(H);
            const float
              f = (i&1)?(H-i):(1.0f-H+i),
              m = V*(1.0f-S),
              n = V*(1.0f-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;
          (*this)(x,y,z,0) = (T)(R<0?0:(R>255?255:R));
          (*this)(x,y,z,1) = (T)(G<0?0:(G>255?255:G));
          (*this)(x,y,z,2) = (T)(B<0?0:(B>255?255:B));
        }
      }
      return *this;
    }

    //! Convert color pixels from (R,G,B) to (H,S,L).
    CImg<typename cimg::superset<T,float>::type> get_RGBtoHSL() const {
      typedef typename cimg::superset<T,float>::type restype;
      return CImg<restype>(*this,false).RGBtoHSL();
    }

    //! In-place version of the previous function.
    CImg<T>& RGBtoHSL() {
      if (!is_empty()) {
        if (dim!=3) throw CImgInstanceException("CImg<%s>::RGBtoHSL() : Input image dimension is dim=%u, "
                                                "should be a (R,G,B) image.",pixel_type(),dim);
        cimg_forXYZ(*this,x,y,z) {
          const float
            R = (float)((*this)(x,y,z,0)),
            G = (float)((*this)(x,y,z,1)),
            B = (float)((*this)(x,y,z,2)),
            nR = (R<0?0:(R>255?255:R))/255.0f,
            nG = (G<0?0:(G>255?255:G))/255.0f,
            nB = (B<0?0:(B>255?255:B))/255.0f,
            m = cimg::min(nR,nG,nB),
            M = cimg::max(nR,nG,nB),
            L = 0.5f*(m+M);
          float H = 0, S = 0;
          if (M==m) H = S = 0;
          else {
            const float
              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.0f) H-=6.0f;
            H*=60;
            S = (L<=0.5f)?((M-m)/(M+m)):((M-m)/(2-M-m));
          }
          (*this)(x,y,z,0) = (T)H;
          (*this)(x,y,z,1) = (T)S;
          (*this)(x,y,z,2) = (T)L;
        }
      }
      return *this;
    }

    //! Convert color pixels from (H,S,L) to (R,G,B).
    CImg<T> get_HSLtoRGB() const {
      return (+*this).HSLtoRGB();
    }

    //! In-place version of the previous function.
    CImg<T>& HSLtoRGB() {
      if (!is_empty()) {
        if (dim!=3) throw CImgInstanceException("CImg<%s>::HSLtoRGB() : Input image dimension is dim=%u, "
                                                "should be a (H,S,V) image",pixel_type(),dim);
        cimg_forXYZ(*this,x,y,z) {
          float
            H = (float)((*this)(x,y,z,0)),
            S = (float)((*this)(x,y,z,1)),
            L = (float)((*this)(x,y,z,2));
          const float
            q = L<0.5f?L*(1+S):(L+S-L*S),
            p = 2*L-q,
            h = H/360;
          float
            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 = ntr<1.0f/6?p+(q-p)*6*ntr:(ntr<0.5f?q:(ntr<2.0f/3?p+(q-p)*6*(2.0f/3-ntr):p)),
            G = ntg<1.0f/6?p+(q-p)*6*ntg:(ntg<0.5f?q:(ntg<2.0f/3?p+(q-p)*6*(2.0f/3-ntg):p)),
            B = ntb<1.0f/6?p+(q-p)*6*ntb:(ntb<0.5f?q:(ntb<2.0f/3?p+(q-p)*6*(2.0f/3-ntb):p));
          R*=255; G*=255; B*=255;
          (*this)(x,y,z,0) = (T)(R<0?0:(R>255?255:R));
          (*this)(x,y,z,1) = (T)(G<0?0:(G>255?255:G));
          (*this)(x,y,z,2) = (T)(B<0?0:(B>255?255:B));
        }
      }
      return *this;
    }

    //! Convert color pixels from (R,G,B) to (H,S,I).
    //! Reference: "Digital Image Processing, 2nd. edition", R. Gonzalez and R. Woods. Prentice Hall, 2002.
    CImg<typename cimg::superset<T,float>::type> get_RGBtoHSI() const {
      typedef typename cimg::superset<T,float>::type restype;
      return CImg<restype>(*this,false).RGBtoHSI();
    }

    //! In-place version of the previous function.
    CImg<T>& RGBtoHSI() {
      if (!is_empty()) {
        if (dim!=3) throw CImgInstanceException("CImg<%s>::RGBtoHSI() : Input image dimension is dim=%u, "
                                                "should be a (R,G,B) image.",pixel_type(),dim);
        cimg_forXYZ(*this,x,y,z) {
          const float
            R = (float)((*this)(x,y,z,0)),
            G = (float)((*this)(x,y,z,1)),
            B = (float)((*this)(x,y,z,2)),
            nR = (R<0?0:(R>255?255:R))/255.0f,
            nG = (G<0?0:(G>255?255:G))/255.0f,
            nB = (B<0?0:(B>255?255:B))/255.0f,
            m = cimg::min(nR,nG,nB),
            theta = std::acos(0.5f*((nR-nG)+(nR-nB))/std::sqrt(std::pow(nR-nG,2)+(nR-nB)*(nG-nB)))*180/cimg::valuePI,
            sum = nR + nG + nB;
          float 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;
          (*this)(x,y,z,0) = (T)H;
          (*this)(x,y,z,1) = (T)S;
          (*this)(x,y,z,2) = (T)I;
        }
      }
      return *this;
    }

    //! Convert color pixels from (H,S,I) to (R,G,B).
    //! Reference: "Digital Image Processing, 2nd. edition", R. Gonzalez and R. Woods. Prentice Hall, 2002.
    CImg<T> get_HSItoRGB() const {
      return (+*this).HSItoRGB();
    }

    //! In-place version of the previous function.
    CImg<T>& HSItoRGB() {
      if (!is_empty()) {
        if (dim!=3) throw CImgInstanceException("CImg<%s>::HSItoRGB() : Input image dimension is dim=%u, "
                                                "should be a (H,S,I) image",pixel_type(),dim);
        cimg_forXYZ(*this,x,y,z) {
          float
            H = (float)((*this)(x,y,z,0)),
            S = (float)((*this)(x,y,z,1)),
            I = (float)((*this)(x,y,z,2));
          float R = 0, G = 0, B = 0, a = I*(1-S);
          if (H<120) {
            B = a;
            R = I*(1+S*std::cos(H*cimg::valuePI/180)/std::cos((60-H)*cimg::valuePI/180));
            G = 3*I-(R+B);
          } else if (H<240) {
            H-=120;
            R = a;
            G = I*(1+S*std::cos(H*cimg::valuePI/180)/std::cos((60-H)*cimg::valuePI/180));
            B = 3*I-(R+G);
          } else {
            H-=240;
            G = a;
            B = I*(1+S*std::cos(H*cimg::valuePI/180)/std::cos((60-H)*cimg::valuePI/180));
            R = 3*I-(G+B);
          }
          R*=255; G*=255; B*=255;
          (*this)(x,y,z,0) = (T)(R<0?0:(R>255?255:R));
          (*this)(x,y,z,1) = (T)(G<0?0:(G>255?255:G));
          (*this)(x,y,z,2) = (T)(B<0?0:(B>255?255:B));
        }
      }
      return *this;
    }

    //! Convert color pixels from (R,G,B) to (Y,Cb,Cr)_8 (Thanks to Chen Wang).
    CImg<T> get_RGBtoYCbCr() const {
      return (+*this).RGBtoYCbCr();
    }

    //! In-place version of the previous function.
    CImg<T>& RGBtoYCbCr() {
      if (!is_empty()) {
        if (dim!=3) throw CImgInstanceException("CImg<%s>::RGBtoYCbCr() : Input image dimension is dim=%u, "
                                                "should be a (R,G,B) image (dim=3)",pixel_type(),dim);
        cimg_forXYZ(*this,x,y,z) {
          const float
            R = (float)((*this)(x,y,z,0)),
            G = (float)((*this)(x,y,z,1)),
            B = (float)((*this)(x,y,z,2)),
            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;
          (*this)(x,y,z,0) = (T)(Y<0?0:(Y>255?255:Y));
          (*this)(x,y,z,1) = (T)(Cb<0?0:(Cb>255?255:Cb));
          (*this)(x,y,z,2) = (T)(Cr<0?0:(Cr>255?255:Cr));
        }
      }
      return *this;
    }

    //! Convert color pixels from (Y,Cb,Cr)_8 to (R,G,B).
    CImg<T> get_YCbCrtoRGB() const {
      return (+*this).YCbCrtoRGB();
    }

    //! In-place version of the previous function.
    CImg<T>& YCbCrtoRGB() {
      if (!is_empty()) {
        if (dim!=3) throw CImgInstanceException("CImg<%s>::YCbCrtoRGB() : Input image dimension is dim=%u, "
                                                "should be a (Y,Cb,Cr)_8 image (dim=3)",pixel_type(),dim);
        cimg_forXYZ(*this,x,y,z) {
          const float
            Y  = (float)((*this)(x, y, z, 0) - 16),
            Cb = (float)((*this)(x, y, z, 1) - 128),
            Cr = (float)((*this)(x, y, z, 2) - 128),
            R = (298*Y + 409*Cr + 128)/256,
            G = (298*Y - 100*Cb - 208*Cr + 128)/256,
            B = (298*Y + 516*Cb + 128)/256;
          (*this)(x,y,z,0) = (T)(R<0?0:(R>255?255:R));
          (*this)(x,y,z,1) = (T)(G<0?0:(G>255?255:G));
          (*this)(x,y,z,2) = (T)(B<0?0:(B>255?255:B));
        }
      }
      return *this;
    }

    //! Convert color pixels from (R,G,B) to (Y,U,V).
    CImg<typename cimg::superset<T,float>::type> get_RGBtoYUV() const {
      typedef typename cimg::superset<T,float>::type restype;
      return CImg<restype>(*this,false).RGBtoYUV();
    }

    //! In-place version of the previous function.
    CImg<T>& RGBtoYUV() {
      if (!is_empty()) {
        if (dim!=3) throw CImgInstanceException("CImg<%s>::RGBtoYUV() : Input image dimension is dim=%u, "
                                                "should be a (R,G,B) image (dim=3)",pixel_type(),dim);
        cimg_forXYZ(*this,x,y,z) {
          const float
            R = (*this)(x,y,z,0)/255.0f,
            G = (*this)(x,y,z,1)/255.0f,
            B = (*this)(x,y,z,2)/255.0f,
            Y = (T)(0.299*R + 0.587*G + 0.114*B);
          (*this)(x,y,z,0) = (T)Y;
          (*this)(x,y,z,1) = (T)(0.492*(B-Y));
          (*this)(x,y,z,2) = (T)(0.877*(R-Y));
        }
      }
      return *this;
    }

    //! Convert color pixels from (Y,U,V) to (R,G,B).
    CImg<T> get_YUVtoRGB() const {
      return (+*this).YUVtoRGB();
    }

    //! In-place version of the previous function.
    CImg<T>& YUVtoRGB() {
      if (!is_empty()) {
        if (dim!=3) throw CImgInstanceException("CImg<%s>::YUVtoRGB() : Input image dimension is dim=%u, "
                                                "should be a (Y,U,V) image (dim=3)",pixel_type(),dim);
        cimg_forXYZ(*this,x,y,z) {
          const T Y = (*this)(x,y,z,0), U = (*this)(x,y,z,1), V = (*this)(x,y,z,2);
          (*this)(x,y,z,0) = (T)((Y + 1.140*V)*255.0f);
          (*this)(x,y,z,1) = (T)((Y - 0.395*U - 0.581*V)*255.0f);
          (*this)(x,y,z,2) = (T)((Y + 2.032*U)*255.0f);
        }
      }
      return *this;
    }

    //! Convert color pixels from (R,G,B) to (X,Y,Z)_709.
    CImg<typename cimg::superset<T,float>::type> get_RGBtoXYZ() const {
      typedef typename cimg::superset<T,float>::type restype;
      return CImg<restype>(*this,false).RGBtoXYZ();
    }

    //! In-place version of the previous function.
    CImg<T>& RGBtoXYZ() {
      if (!is_empty()) {
        if (dim!=3) throw CImgInstanceException("CImg<%s>::RGBtoXYZ() : Input image dimension is dim=%u, "
                                                "should be a (R,G,B) image (dim=3)",pixel_type(),dim);
        cimg_forXYZ(*this,x,y,z) {
          const float
            R = (float)((*this)(x,y,z,0)/255.0f),
            G = (float)((*this)(x,y,z,1)/255.0f),
            B = (float)((*this)(x,y,z,2)/255.0f);
          (*this)(x,y,z,0) = (T)(0.412453*R + 0.357580*G + 0.180423*B);
          (*this)(x,y,z,1) = (T)(0.212671*R + 0.715160*G + 0.072169*B);
          (*this)(x,y,z,2) = (T)(0.019334*R + 0.119193*G + 0.950227*B);
        }
      }
      return *this;
    }

    //! Convert (X,Y,Z)_709 pixels of a color image into the (R,G,B) color space.
    CImg<T> get_XYZtoRGB() const {
      return (+*this).XYZtoRGB();
    }

    //! In-place version of the previous function.
    CImg<T>& XYZtoRGB() {
      if (!is_empty()) {
        if (dim!=3) throw CImgInstanceException("CImg<%s>::XYZtoRGB() : Input image dimension is dim=%u, "
                                                "should be a (X,Y,Z) image (dim=3)",pixel_type(),dim);
        cimg_forXYZ(*this,x,y,z) {
          const float
            X = (float)(255.0f*(*this)(x,y,z,0)),
            Y = (float)(255.0f*(*this)(x,y,z,1)),
            Z = (float)(255.0f*(*this)(x,y,z,2));
          (*this)(x,y,z,0) = (T)(3.240479*X  - 1.537150*Y - 0.498535*Z);
          (*this)(x,y,z,1) = (T)(-0.969256*X + 1.875992*Y + 0.041556*Z);
          (*this)(x,y,z,2) = (T)(0.055648*X  - 0.204043*Y + 1.057311*Z);
        }
      }
      return *this;
    }

    //! Convert (X,Y,Z)_709 pixels of a color image into the (L*,a*,b*) color space.
    CImg<T> get_XYZtoLab() const {
      return (+*this).XYZtoLab();
    }

    //! In-place version of the previous function.
    CImg<T>& XYZtoLab() {
#define cimg_Labf(x)  ((x)>=0.008856?(std::pow(x,1/3.0)):(7.787*(x)+16.0/116.0))
      if (!is_empty()) {
        if (dim!=3) throw CImgInstanceException("CImg<%s>::XYZtoLab() : Input image dimension is dim=%u, "
                                                "should be a (X,Y,Z) image (dim=3)",pixel_type(),dim);
        const double
          Xn = 0.412453 + 0.357580 + 0.180423,
          Yn = 0.212671 + 0.715160 + 0.072169,
          Zn = 0.019334 + 0.119193 + 0.950227;
        cimg_forXYZ(*this,x,y,z) {
          const T X = (*this)(x,y,z,0), Y = (*this)(x,y,z,1), Z = (*this)(x,y,z,2);
          const double
            XXn = X/Xn, YYn = Y/Yn, ZZn = Z/Zn,
            fX = cimg_Labf(XXn), fY = cimg_Labf(YYn), fZ = cimg_Labf(ZZn);
          (*this)(x,y,z,0) = (T)(116*fY-16);
          (*this)(x,y,z,1) = (T)(500*(fX-fY));
          (*this)(x,y,z,2) = (T)(200*(fY-fZ));
        }
      }
      return *this;
    }

    //! Convert (L,a,b) pixels of a color image into the (X,Y,Z) color space.
    CImg<T> get_LabtoXYZ() const {
      return (+*this).LabtoXYZ();
    }

    //! In-place version of the previous function.
    CImg<T>& LabtoXYZ() {
#define cimg_Labfi(x) ((x)>=0.206893?((x)*(x)*(x)):(((x)-16.0/116.0)/7.787))
      if (!is_empty()) {
        if (dim!=3) throw CImgInstanceException("CImg<%s>::LabtoXYZ() : Input image dimension is dim=%u, "
                                                "should be a (X,Y,Z) image (dim=3)",pixel_type(),dim);
        const double
          Xn = 0.412453 + 0.357580 + 0.180423,
          Yn = 0.212671 + 0.715160 + 0.072169,
          Zn = 0.019334 + 0.119193 + 0.950227;
        cimg_forXYZ(*this,x,y,z) {
          const T L = (*this)(x,y,z,0), a = (*this)(x,y,z,1), b = (*this)(x,y,z,2);
          const double
            cY = (L+16)/116.0,
            Y = Yn*cimg_Labfi(cY),
            pY = std::pow(Y/Yn,1.0/3),
            cX = a/500+pY,
            X = Xn*cX*cX*cX,
            cZ = pY-b/200,
            Z = Zn*cZ*cZ*cZ;
          (*this)(x,y,z,0) = (T)(X);
          (*this)(x,y,z,1) = (T)(Y);
          (*this)(x,y,z,2) = (T)(Z);
        }
      }
      return *this;
    }

    //! Convert (X,Y,Z)_709 pixels of a color image into the (x,y,Y) color space.
    CImg<T> get_XYZtoxyY() const {
      return (+*this).XYZtoxyY();
    }

    //! In-place version of the previous function.
    CImg<T>& XYZtoxyY() {
      if (!is_empty()) {
        if (dim!=3) throw CImgInstanceException("CImg<%s>::XYZtoxyY() : Input image dimension is dim=%u, "
                                                "should be a (X,Y,Z) image (dim=3)",pixel_type(),dim);
        cimg_forXYZ(*this,x,y,z) {
          const T X = (*this)(x,y,z,0), Y = (*this)(x,y,z,1), Z = (*this)(x,y,z,2), sum = (X+Y+Z), nsum = sum>0?sum:1;
          (*this)(x,y,z,0) = X/nsum;
          (*this)(x,y,z,1) = Y/nsum;
          (*this)(x,y,z,2) = Y;
        }
      }
      return *this;
    }

    //! Convert (x,y,Y) pixels of a color image into the (X,Y,Z)_709 color space.
    CImg<T> get_xyYtoXYZ() const {
      return (+*this).xyYtoXYZ();
    }

    //! In-place version of the previous function.
    CImg<T>& xyYtoXYZ() {
      if (!is_empty()) {
        if (dim!=3) throw CImgInstanceException("CImg<%s>::xyYtoXYZ() : Input image dimension is dim=%u, "
                                                "should be a (x,y,Y) image (dim=3)",pixel_type(),dim);
        cimg_forXYZ(*this,x,y,z) {
          const T px = (*this)(x,y,z,0), py = (*this)(x,y,z,1), Y = (*this)(x,y,z,2), ny = py>0?py:1;
          (*this)(x,y,z,0) = (T)(px*Y/ny);
          (*this)(x,y,z,1) = Y;
          (*this)(x,y,z,2) = (T)((1-px-py)*Y/ny);
        }
      }
      return *this;
    }

    //! Convert a (R,G,B) image to a (L,a,b) one.
    CImg<T> get_RGBtoLab() const {
      return (+*this).RGBtoLab();
    }

    //! In-place version of the previous function.
    CImg<T>& RGBtoLab() {
      return RGBtoXYZ().XYZtoLab();
    }

    //! Convert a (L,a,b) image to a (R,G,B) one.
    CImg<T> get_LabtoRGB() const {
      return (+*this).LabtoRGB();
    }

    //! In-place version of the previous function.
    CImg<T>& LabtoRGB() {
      return LabtoXYZ().XYZtoRGB();
    }

    //! Convert a (R,G,B) image to a (x,y,Y) one.
    CImg<T> get_RGBtoxyY() const {
      return (+*this).RGBtoxyY();
    }

    //! In-place version of the previous function.
    CImg<T>& RGBtoxyY() {
      return RGBtoXYZ().XYZtoxyY();
    }

    //! Convert a (x,y,Y) image to a (R,G,B) one.
    CImg<T> get_xyYtoRGB() const {
      return (+*this).xyYtoRGB();
    }

    //! In-place version of the previous function.
    CImg<T>& xyYtoRGB() {
      return xyYtoXYZ().XYZtoRGB();
    }

    //! Convert a (R,G,B) image to a Bayer-coded representation.
    /**
       \note First (upper-left) pixel if the red component of the pixel color.
    **/
    CImg<T> get_RGBtoBayer() const {
      if (is_empty()) return *this;
      if (dim!=3) throw CImgInstanceException("CImg<%s>::RGBtoBayer() : Input image dimension is dim=%u, "
                                              "should be a (R,G,B) image (dim=3)",pixel_type(),dim);
      CImg<T> res(width,height,depth,1);
      const T
        *pR = ptr(0,0,0,0),
        *pG = ptr(0,0,0,1),
        *pB = ptr(0,0,0,2);
      T *ptrd = res.data;
      cimg_forXYZ(*this,x,y,z) {
        if (y%2) {
          if (x%2) *(ptrd++) = *pB;
          else *(ptrd++) = *pG;
        } else {
          if (x%2) *(ptrd++) = *pG;
          else *(ptrd++) = *pR;
        }
        ++pR; ++pG; ++pB;
      }
      return res;
    }

    // In-place version of the previous function.
    CImg<T>& RGBtoBayer() {
      return get_RGBtoBayer().transfer_to(*this);
    }

    //! Convert a Bayer-coded image to a (R,G,B) color image.
    CImg<T> get_BayertoRGB(const unsigned int interpolation_type=3) const {
      if (is_empty()) return *this;
      if (dim!=1) throw CImgInstanceException("CImg<%s>::BayertoRGB() : Input image dimension is dim=%u, "
                                              "should be a Bayer image (dim=1)",pixel_type(),dim);
      CImg<T> res(width,height,depth,3);
      CImg_3x3(I,T);
      T
        *pR = res.ptr(0,0,0,0),
        *pG = res.ptr(0,0,0,1),
        *pB = res.ptr(0,0,0,2);
      switch(interpolation_type) {
      case 3: { // Edge-directed
        CImg_3x3(R,T);
        CImg_3x3(G,T);
        CImg_3x3(B,T);
        cimg_forXYZ(*this,x,y,z) {
          const int _p1x = x?x-1:1, _p1y = y?y-1:1, _n1x = x<dimx()-1?x+1:x-1, _n1y = y<dimy()-1?y+1:y-1;
          cimg_get3x3(*this,x,y,z,0,I);
          if (y%2) {
            if (x%2) {
              const float alpha = cimg::sqr(Inc-Ipc), beta = cimg::sqr(Icn-Icp), cx = 1/(1+alpha), cy = 1/(1+beta);
              *pG = (T)((cx*(Inc+Ipc) + cy*(Icn+Icp))/(2*(cx+cy)));
            } else *pG = Icc;
          } else {
            if (x%2) *pG = Icc;
            else {
              const float alpha = cimg::sqr(Inc-Ipc), beta = cimg::sqr(Icn-Icp), cx = 1/(1+alpha), cy = 1/(1+beta);
              *pG = (T)((cx*(Inc+Ipc) + cy*(Icn+Icp))/(2*(cx+cy)));
            }
          }
          ++pG;
        }
        cimg_forXYZ(*this,x,y,z) {
          const int _p1x = x?x-1:1, _p1y = y?y-1:1, _n1x = x<dimx()-1?x+1:x-1, _n1y = y<dimy()-1?y+1:y-1;
          cimg_get3x3(*this,x,y,z,0,I);
          cimg_get3x3(res,x,y,z,1,G);
          if (y%2) {
            if (x%2) *pB = Icc;
            else { *pR = (T)((Icn+Icp)/2); *pB = (T)((Inc+Ipc)/2); }
          } else {
            if (x%2) { *pR = (T)((Inc+Ipc)/2); *pB = (T)((Icn+Icp)/2); }
            else *pR = Icc;
          }
          ++pR; ++pB;
        }
        pR = res.ptr(0,0,0,0);
        pG = res.ptr(0,0,0,1);
        pB = res.ptr(0,0,0,2);
        cimg_forXYZ(*this,x,y,z) {
          const int _p1x = x?x-1:1, _p1y = y?y-1:1, _n1x = x<dimx()-1?x+1:x-1, _n1y = y<dimy()-1?y+1:y-1;
          cimg_get3x3(res,x,y,z,0,R);
          cimg_get3x3(res,x,y,z,1,G);
          cimg_get3x3(res,x,y,z,2,B);
          if (y%2) {
            if (x%2) {
              const float alpha = cimg::sqr(Rnc-Rpc), beta = cimg::sqr(Rcn-Rcp), cx = 1/(1+alpha), cy = 1/(1+beta);
              *pR = (T)((cx*(Rnc+Rpc) + cy*(Rcn+Rcp))/(2*(cx+cy)));
            }
          } else {
            if (!(x%2)) {
              const float alpha = cimg::sqr(Bnc-Bpc), beta = cimg::sqr(Bcn-Bcp), cx = 1/(1+alpha), cy = 1/(1+beta);
              *pB = (T)((cx*(Bnc+Bpc) + cy*(Bcn+Bcp))/(2*(cx+cy)));
            }
          }
          ++pR; ++pG; ++pB;
        }
      } break;
      case 2: { // Linear interpolation
        cimg_forXYZ(*this,x,y,z) {
          const int _p1x = x?x-1:1, _p1y = y?y-1:1, _n1x = x<dimx()-1?x+1:x-1, _n1y = y<dimy()-1?y+1:y-1;
          cimg_get3x3(*this,x,y,z,0,I);
          if (y%2) {
            if (x%2) { *pR = (Ipp+Inn+Ipn+Inp)/4; *pG = (Inc+Ipc+Icn+Icp)/4; *pB = Icc; }
            else { *pR = (Icp+Icn)/2; *pG = Icc; *pB = (Inc+Ipc)/2; }
          } else {
            if (x%2) { *pR = (Ipc+Inc)/2; *pG = Icc; *pB = (Icn+Icp)/2; }
            else { *pR = Icc; *pG = (Inc+Ipc+Icn+Icp)/4; *pB = (Ipp+Inn+Ipn+Inp)/4; }
          }
          ++pR; ++pG; ++pB;
        }
      } break;
      case 1: { // Nearest neighbor interpolation
        cimg_forXYZ(*this,x,y,z) {
          const int _p1x = x?x-1:1, _p1y = y?y-1:1, _n1x = x<dimx()-1?x+1:x-1, _n1y = y<dimy()-1?y+1:y-1;
          cimg_get3x3(*this,x,y,z,0,I);
          if (y%2) {
            if (x%2) { *pR = cimg::min(Ipp,Inn,Ipn,Inp); *pG = cimg::min(Inc,Ipc,Icn,Icp); *pB = Icc; }
            else { *pR = cimg::min(Icn,Icp); *pG = Icc; *pB = cimg::min(Inc,Ipc); }
          } else {
            if (x%2) { *pR = cimg::min(Inc,Ipc); *pG = Icc; *pB = cimg::min(Icn,Icp); }
            else { *pR = Icc; *pG = cimg::min(Inc,Ipc,Icn,Icp); *pB = cimg::min(Ipp,Inn,Ipn,Inp); }
          }
          ++pR; ++pG; ++pB;
        }
      } break;
      default: { // 0-filling interpolation
        const T *ptrs = data;
        res.fill(0);
        cimg_forXYZ(*this,x,y,z) {
          const T val = *(ptrs++);
          if (y%2) { if (x%2) *pB = val; else *pG = val; } else { if (x%2) *pG = val; else *pR = val; }
          ++pR; ++pG; ++pB;
        }
      } break;
      }
      return res;
    }

    // In-place version of the previous function.
    CImg<T>& BayertoRGB(const unsigned int interpolation_type=3) {
      return get_BayertoRGB(interpolation_type).transfer_to(*this);
    }

    //@}
    //-------------------
    //
    //! \name Drawing
    //@{
    //-------------------

    // The following _draw_scanline() routines are a *non user-friendly function*, used only by inner functions
    // 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.0f, const float brightness=1.0f, const bool init=false) {
      static const T maxval = (T)cimg::min(cimg::type<T>::max(),cimg::type<tc>::max());
      static float nopacity = 0, copacity = 0;
      static unsigned int whz = 0;
      static const tc *col = 0;
      if (init) {
        nopacity = cimg::abs(opacity);
        copacity = 1.0f - cimg::max(opacity,0.0f);
        whz = width*height*depth;
      } else {
        const int nx0 = x0>0?x0:0, nx1 = x1<dimx()?x1:dimx()-1, dx = nx1 - nx0;
        if (dx>=0) {
          col = color;
          const unsigned int off = whz-dx-1;
          T *ptrd = ptr(nx0,y);
          if (opacity>=1) { // ** Opaque drawing **
            if (brightness==1) { // Brightness==1
              if (sizeof(T)!=1) cimg_forV(*this,k) {
                const T val = (T)*(col++);
                for (int x=dx; x>=0; --x) *(ptrd++) = val;
                ptrd+=off;
              } else cimg_forV(*this,k) {
                const T val = (T)*(col++);
                std::memset(ptrd,(int)val,dx+1);
                ptrd+=whz;
              }
            } else if (brightness<1) { // Brightness<1
              if (sizeof(T)!=1) cimg_forV(*this,k) {
                const T val = (T)(*(col++)*brightness);
                for (int x=dx; x>=0; --x) *(ptrd++) = val;
                ptrd+=off;
              } else cimg_forV(*this,k) {
                const T val = (T)(*(col++)*brightness);
                std::memset(ptrd,(int)val,dx+1);
                ptrd+=whz;
              }
            } else { // Brightness>1
              if (sizeof(T)!=1) cimg_forV(*this,k) {
                const T val = (T)((2-brightness)**(col++) + (brightness-1)*maxval);
                for (int x=dx; x>=0; --x) *(ptrd++) = val;
                ptrd+=off;
              } else cimg_forV(*this,k) {
                const T val = (T)((2-brightness)**(col++) + (brightness-1)*maxval);
                std::memset(ptrd,(int)val,dx+1);
                ptrd+=whz;
              }
            }
          } else { // ** Transparent drawing **
            if (brightness==1) { // Brightness==1
              cimg_forV(*this,k) {
                const T val = (T)*(col++);
                for (int x=dx; x>=0; --x) { *ptrd = (T)(val*nopacity + *ptrd*copacity); ++ptrd; }
                ptrd+=off;
              }
            } else if (brightness<=1) { // Brightness<1
              cimg_forV(*this,k) {
                const T val = (T)(*(col++)*brightness);
                for (int x=dx; x>=0; --x) { *ptrd = (T)(val*nopacity + *ptrd*copacity); ++ptrd; }
                ptrd+=off;
              }
            } else { // Brightness>1
              cimg_forV(*this,k) {
                const T val = (T)((2-brightness)**(col++) + (brightness-1)*maxval);
                for (int x=dx; x>=0; --x) { *ptrd = (T)(val*nopacity + *ptrd*copacity); ++ptrd; }
                ptrd+=off;
              }
            }
          }
        }
      }
      return *this;
    }

    template<typename tc>
      CImg<T>& _draw_scanline(const tc *const color, const float opacity=1.0f) {
      return _draw_scanline(0,0,0,color,opacity,0,true);
    }

    //! Draw a colored point (pixel) in the instance image.
    /**
       \param x0 X-coordinate of the point.
       \param y0 Y-coordinate of the point.
       \param color Pointer to (or image of) \c dimv() consecutive values, defining the color channels.
       \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
       \see CImg::operator()().
    **/
    template<typename tc>
      CImg<T>& draw_point(const int x0, const int y0, const tc *const color, const float opacity=1.0f) {
      return draw_point(x0,y0,0,color,opacity);
    }

    template<typename tc>
      CImg<T>& draw_point(const int x0, const int y0, const CImg<tc>& color, const float opacity=1.0f) {
      return draw_point(x0,y0,color.data,opacity);
    }

    //! Draw a colored point (pixel) in the instance image (for volumetric images).
    /**
       \note
       - Similar to CImg::draw_point() for 3D volumetric images.
    **/
    template<typename tc>
      CImg<T>& draw_point(const int x0, const int y0, const int z0, const tc *const color, const float opacity=1.0f) {
      if (!is_empty()) {
        if (!color) throw CImgArgumentException("CImg<%s>::draw_point() : Specified color is (null)",pixel_type());
        if (x0>=0 && y0>=0 && z0>=0 && x0<dimx() && y0<dimy() && z0<dimz()) {
          const tc *col = color;
          const unsigned int whz = width*height*depth;
          const float nopacity = cimg::abs(opacity), copacity = 1-cimg::max(opacity,0.0f);
          T *ptrd = ptr(x0,y0,z0,0);
          if (opacity>=1) cimg_forV(*this,k) { *ptrd = (T)*(col++); ptrd+=whz; }
          else cimg_forV(*this,k) { *ptrd = (T)(*(col++)*nopacity + *ptrd*copacity); ptrd+=whz; }
        }
      }
      return *this;
    }

    template<typename tc>
      CImg<T>& draw_point(const int x0, const int y0, const int z0, const CImg<tc>& color, const float opacity=1.0f) {
      return draw_point(x0,y0,z0,color.data,opacity);
    }

    // Inner routine for drawing a cloud of points with generic type for coordinates.
    template<typename t, typename tc>
      CImg<T>& _draw_point(const t& points, const tc *const color,
                           const float opacity, const unsigned int W, const unsigned int H) {
      if (points && W && H>1) {
        if (H==2) for (unsigned int i=0; i<W; ++i) {
          const int x = (int)points(i,0), y = (int)points(i,1);
          draw_point(x,y,color,opacity);
        } else for (unsigned int i=0; i<W; ++i) {
          const int x = (int)points(i,0), y = (int)points(i,1), z = (int)points(i,2);
          draw_point(x,y,z,color,opacity);
        }
      }
      return *this;
    }

    //! Draw a cloud of colored points in the instance image.
    /**
       \param points Coordinates of vertices, stored as a list of vectors.
       \param color Pointer to \c dimv() consecutive values of type \c T, defining the drawing color.
       \param opacity Drawing opacity (optional).
       \note
       - This function uses several call to the single CImg::draw_point() procedure,
       depending on the vectors size in \p points.
       \par Example:
       \code
       CImg<unsigned char> img(100,100,1,3,0);
       const unsigned char color[] = { 255,128,64 };
       CImgList<int> points;
       points.insert(CImg<int>::vector(0,0)).
             .insert(CImg<int>::vector(70,10)).
             .insert(CImg<int>::vector(80,60)).
             .insert(CImg<int>::vector(10,90));
       img.draw_point(points,color);
       \endcode
       \sa CImg::draw_point().
    **/
    template<typename t, typename tc>
      CImg<T>& draw_point(const CImgList<t>& points, const tc *const color, const float opacity=1.0f) {
      unsigned int H = ~0U; cimglist_for(points,p) H = cimg::min(H,(unsigned int)(points[p].size()));
      return _draw_point(points,color,opacity,points.size,H);
    }

    template<typename t, typename tc>
      CImg<T>& draw_point(const CImgList<t>& points, const CImg<tc>& color, const float opacity=1.0f) {
      return draw_point(points,color.data,opacity);
    }

    //! Draw a cloud of points in the instance image.
    /**
       \note
       - Similar to the previous function, where the N vertex coordinates are stored as a Nx2 or Nx3 image
       (sequence of vectors aligned along the x-axis).
    **/
    template<typename t, typename tc>
      CImg<T>& draw_point(const CImg<t>& points, const tc *const color, const float opacity=1.0f) {
      return _draw_point(points,color,opacity,points.width,points.height);
    }

    template<typename t, typename tc>
      CImg<T>& draw_point(const CImg<t>& points, const CImg<tc>& color, const float opacity=1.0f) {
      return draw_point(points,color.data,opacity);
    }

    //! Draw a colored line in the instance image.
    /**
       \param x0 X-coordinate of the starting line point.
       \param y0 Y-coordinate of the starting line point.
       \param x1 X-coordinate of the ending line point.
       \param y1 Y-coordinate of the ending line point.
       \param color Pointer to \c dimv() consecutive values of type \c T, defining the drawing color.
       \param opacity Drawing opacity (optional).
       \param pattern An integer whose bits describe the line pattern (optional).
       \param init_hatch Flag telling if a reinitialization of the hash state must be done (optional).
       \note
       - Clipping is supported.
       - Line routine uses Bresenham's algorithm.
       - Set \p init_hatch = false to draw consecutive hatched segments without breaking the line pattern.
       \par Example:
       \code
       CImg<unsigned char> img(100,100,1,3,0);
       const unsigned char color[] = { 255,128,64 };
        img.draw_line(40,40,80,70,color);
       \endcode
    **/
    template<typename tc>
      CImg<T>& draw_line(const int x0, const int y0, const int x1, const int y1,
                         const tc *const color, const float opacity=1.0f,
                         const unsigned int pattern=~0U, const bool init_hatch=true) {
      if (!is_empty()) {
        if (!color) throw CImgArgumentException("CImg<%s>::draw_line() : Specified color is (null)",pixel_type());
	static unsigned int hatch = ~0U-(~0U>>1);
	if (init_hatch) hatch = ~0U-(~0U>>1);
        const tc* col = color;
	const bool xdir = x0<x1, ydir = y0<y1;
	int
	  nx0 = x0, nx1 = x1, ny0 = y0, ny1 = y1,
	  &xleft = xdir?nx0:nx1, &yleft = xdir?ny0:ny1,
          &xright = xdir?nx1:nx0, &yright = xdir?ny1:ny0,
	  &xup = ydir?nx0:nx1, &yup = ydir?ny0:ny1,
          &xdown = ydir?nx1:nx0, &ydown = ydir?ny1:ny0;
	if (xright<0 || xleft>=dimx()) return *this;
        if (xleft<0) { yleft-=xleft*(yright-yleft)/(xright-xleft); xleft = 0; }
        if (xright>=dimx()) { yright-=(xright-dimx())*(yright-yleft)/(xright-xleft); xright = dimx()-1; }
	if (ydown<0 || yup>=dimy()) return *this;
        if (yup<0) { xup-=yup*(xdown-xup)/(ydown-yup); yup = 0; }
        if (ydown>=dimy()) { xdown-=(ydown-dimy())*(xdown-xup)/(ydown-yup); ydown = dimy()-1; }
	T *ptrd0 = ptr(nx0,ny0,0,0);
	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) {
	    T *ptrd = ptrd0;
	    if (pattern&hatch) { cimg_forV(*this,k) { *ptrd = (T)*(col++); ptrd+=wh; } col-=dim; }
	    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;
	    cimg_forV(*this,k) { *ptrd = (T)*(col++); ptrd+=wh; } col-=dim;
	    ptrd0+=offx; if ((error-=dy)<0) { ptrd0+=offy; error+=dx; }
	  }
	} else {
          const float nopacity = cimg::abs(opacity), copacity=1-cimg::max(opacity,0.0f);
	  if (~pattern) for (int error=dx>>1, x=0; x<=dx; ++x) {
	    T *ptrd = ptrd0;
	    if (pattern&hatch) { cimg_forV(*this,k) { const tc& c = *(col++); *ptrd = (T)(c*nopacity + *ptrd*copacity); ptrd+=wh; } col-=dim; }
	    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;
	    cimg_forV(*this,k) { const tc& c = *(col++); *ptrd = (T)(c*nopacity + *ptrd*copacity); ptrd+=wh; } col-=dim;
	    ptrd0+=offx; if ((error-=dy)<0) { ptrd0+=offy; error+=dx; }
	  }
	}
      }
      return *this;
    }

    template<typename tc>
      CImg<T>& draw_line(const int x0, const int y0, const int x1, const int y1,
                         const CImg<tc>& color, const float opacity=1.0f,
                         const unsigned int pattern=~0U, const bool init_hatch=true) {
      return draw_line(x0,y0,x1,y1,color.data,opacity,pattern,init_hatch);
    }

    //! Draw a colored line in the instance image (for volumetric images).
    /**
       \note
       - Similar to CImg::draw_line() for 3D volumetric images.
    **/
    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.0f,
                         const unsigned int pattern=~0U, const bool init_hatch=true) {
      if (!is_empty()) {
        if (!color) throw CImgArgumentException("CImg<%s>::draw_line() : Specified color is (null)",pixel_type());
	static unsigned int hatch = ~0U-(~0U>>1);
	if (init_hatch) hatch = ~0U-(~0U>>1);
        const tc *col = color;
        int nx0 = x0, ny0 = y0, nz0 = z0, nx1 = x1, ny1 = y1, nz1 = z1;
        if (nx0>nx1) cimg::swap(nx0,nx1,ny0,ny1,nz0,nz1);
        if (nx1<0 || nx0>=dimx()) return *this;
        if (nx0<0) { const int D=1+nx1-nx0; ny0-=nx0*(1+ny1-ny0)/D; nz0-=nx0*(1+nz1-nz0)/D; nx0 = 0; }
        if (nx1>=dimx()) { const int d=nx1-dimx(), D=1+nx1-nx0; ny1+=d*(1+ny0-ny1)/D; nz1+=d*(1+nz0-nz1)/D; nx1 = dimx()-1; }
        if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,nz0,nz1);
        if (ny1<0 || ny0>=dimy()) return *this;
        if (ny0<0) { const int D=1+ny1-ny0; nx0-=ny0*(1+nx1-nx0)/D; nz0-=ny0*(1+nz1-nz0)/D; ny0 = 0; }
        if (ny1>=dimy()) { const int d=ny1-dimy(), D=1+ny1-ny0; nx1+=d*(1+nx0-nx1)/D; nz1+=d*(1+nz0-nz1)/D; ny1 = dimy()-1; }
        if (nz0>nz1) cimg::swap(nx0,nx1,ny0,ny1,nz0,nz1);
        if (nz1<0 || nz0>=dimz()) return *this;
        if (nz0<0) { const int D=1+nz1-nz0; nx0-=nz0*(1+nx1-nx0)/D; ny0-=nz0*(1+ny1-ny0)/D; nz0 = 0; }
        if (nz1>=dimz()) { const int d=nz1-dimz(), D=1+nz1-nz0; nx1+=d*(1+nx0-nx1)/D; ny1+=d*(1+ny0-ny1)/D; nz1 = dimz()-1; }
        const unsigned int dmax = cimg::max(cimg::abs(nx1-nx0),cimg::abs(ny1-ny0),nz1-nz0), whz = width*height*depth;
        const float px = (nx1-nx0)/(float)dmax, py = (ny1-ny0)/(float)dmax, pz = (nz1-nz0)/(float)dmax;
        float x = (float)nx0, y = (float)ny0, z = (float)nz0;
        if (opacity>=1) for (unsigned int t=0; t<=dmax; ++t) {
          if (!(~pattern) || (~pattern && pattern&hatch)) {
            T* ptrd = ptr((unsigned int)x,(unsigned int)y,(unsigned int)z,0);
            cimg_forV(*this,k) { *ptrd = (T)*(col++); ptrd+=whz; }
            col-=dim;
          }
          x+=px; y+=py; z+=pz; if (pattern) hatch=(hatch<<1)+(hatch>>(sizeof(unsigned int)*8-1));
        } else {
          const float nopacity = cimg::abs(opacity), copacity = 1-cimg::max(opacity,0.0f);
          for (unsigned int t=0; t<=dmax; ++t) {
            if (!(~pattern) || (~pattern && pattern&hatch)) {
              T* ptrd = ptr((unsigned int)x,(unsigned int)y,(unsigned int)z,0);
              cimg_forV(*this,k) { *ptrd = (T)(*(col++)*nopacity + *ptrd*copacity); ptrd+=whz; }
              col-=dim;
            }
            x+=px; y+=py; z+=pz; if (pattern) hatch=(hatch<<1)+(hatch>>(sizeof(unsigned int)*8-1));
          }
        }
      }
      return *this;
    }

    template<typename tc>
      CImg<T>& draw_line(const int x0, const int y0, const int z0, const int x1, const int y1, const int z1,
                         const CImg<tc>& color, const float opacity=1.0f,
                         const unsigned int pattern=~0U, const bool init_hatch=true) {
      return draw_line(x0,y0,z0,x1,y1,z1,color.data,opacity,pattern,init_hatch);
    }

    //! Draw a textured line in the instance image.
    /**
       \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 t>
      CImg<T>& draw_line(const int x0, const int y0, const int x1, const int y1,
                         const CImg<t>& texture,
                         const int tx0, const int ty0, const int tx1, const int ty1,
                         const float opacity=1.0f, const unsigned int pattern=~0U, const bool init_hatch=true) {
      if (!is_empty()) {
        if (!texture || texture.dim<dim)
          throw CImgArgumentException("CImg<%s>::draw_line() : Specified texture (%u,%u,%u,%u,%p) is not a valid argument.",
                                      pixel_type(),texture.width,texture.height,texture.depth,texture.dim,texture.data);
        if (is_overlapping(texture)) return draw_line(x0,y0,x1,y1,+texture,tx0,ty0,tx1,ty1,opacity,pattern,init_hatch);
	static unsigned int hatch = ~0U-(~0U>>1);
	if (init_hatch) hatch = ~0U-(~0U>>1);
	const bool xdir = x0<x1, ydir = y0<y1;
	int
	  dtx = tx1-tx0, dty = ty1-ty0,
	  nx0  = x0,  nx1  = x1,  ny0  = y0,  ny1  = y1,
	  tnx0 = tx0, tnx1 = tx1, tny0 = ty0, tny1 = ty1,
	  &xleft  = xdir?nx0:nx1,   &yleft  = xdir?ny0:ny1,   &xright  = xdir?nx1:nx0,   &yright  = xdir?ny1:ny0,
	  &txleft = xdir?tnx0:tnx1, &tyleft = xdir?tny0:tny1, &txright = xdir?tnx1:tnx0, &tyright = xdir?tny1:tny0,
	  &xup    = ydir?nx0:nx1,   &yup    = ydir?ny0:ny1,   &xdown   = ydir?nx1:nx0,   &ydown   = ydir?ny1:ny0,
	  &txup   = ydir?tnx0:tnx1, &tyup   = ydir?tny0:tny1, &txdown  = ydir?tnx1:tnx0, &tydown  = ydir?tny1:tny0;
	if (xright<0 || xleft>=dimx()) return *this;
        if (xleft<0) {
	  const int D = xright-xleft;
	  yleft-=xleft*(yright-yleft)/D;
	  txleft-=xleft*(txright-txleft)/D;
	  tyleft-=xleft*(tyright-tyleft)/D;
	  xleft = 0;
	}
        if (xright>=dimx()) {
	  const int d = xright-dimx(), D = xright-xleft;
	  yright-=d*(yright-yleft)/D;
	  txright-=d*(txright-txleft)/D;
	  tyright-=d*(tyright-tyleft)/D;
	  xright = dimx()-1;
	}
	if (ydown<0 || yup>=dimy()) return *this;
        if (yup<0) {
	  const int D = ydown-yup;
	  xup-=yup*(xdown-xup)/D;
	  txup-=yup*(txdown-txup)/D;
	  tyup-=yup*(tydown-tyup)/D;
	  yup = 0;
	}
        if (ydown>=dimy()) {
	  const int d = ydown-dimy(), D = ydown-yup;
	  xdown-=d*(xdown-xup)/D;
	  txdown-=d*(txdown-txup)/D;
	  tydown-=d*(tydown-tyup)/D;
	  ydown = dimy()-1;
	}
	T *ptrd0 = ptr(nx0,ny0,0,0);
	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) {
	    T *ptrd = ptrd0;
	    if (pattern&hatch) {
	      const int tx = tx0 + x*dtx/ndx, ty = ty0 + x*dty/ndx;
	      cimg_forV(*this,k) { *ptrd = texture(tx,ty,0,k); ptrd+=wh; }
	    }
	    hatch>>=1; if (!hatch) hatch = ~0U-(~0U>>1);
	    ptrd0+=offx; if ((error-=dy)<0) { ptrd0+=offy; error+=dx; }
	  } else for (int error=dx>>1, x=0; x<=dx; ++x) {
	    T *ptrd = ptrd0;
	    const int tx = tx0 + x*dtx/ndx, ty = ty0 + x*dty/ndx;
	    cimg_forV(*this,k) { *ptrd = texture(tx,ty,0,k); ptrd+=wh; }
	    ptrd0+=offx; if ((error-=dy)<0) { ptrd0+=offy; error+=dx; }
	  }
	} else {
          const float nopacity = cimg::abs(opacity), copacity=1-cimg::max(opacity,0.0f);
	  if (~pattern) for (int error=dx>>1, x=0; x<=dx; ++x) {
	    T *ptrd = ptrd0;
	    if (pattern&hatch) {
	      const int tx = tx0 + x*dtx/ndx, ty = ty0 + x*dty/ndx;
	      cimg_forV(*this,k) { const T c = (T)texture(tx,ty,0,k); *ptrd = (T)(c*nopacity + *ptrd*copacity); ptrd+=wh; }
	    }
	    hatch>>=1; if (!hatch) hatch = ~0U-(~0U>>1);
	    ptrd0+=offx; if ((error-=dy)<0) { ptrd0+=offy; error+=dx; }
	  } else for (int error=dx>>1, x=0; x<=dx; ++x) {
	    T *ptrd = ptrd0;
	    const int tx = tx0 + x*dtx/ndx, ty = ty0 + x*dty/ndx;
	    cimg_forV(*this,k) { const T c = (T)texture(tx,ty,0,k); *ptrd = (T)(c*nopacity + *ptrd*copacity); ptrd+=wh; }
	    ptrd0+=offx; if ((error-=dy)<0) { ptrd0+=offy; error+=dx; }
	  }
	}
      }
      return *this;
    }

    //! Draw a textured line in the instance image, with perspective correction.
    template<typename t>
      CImg<T>& draw_line(const int x0, const int y0, const float z0, const int x1, const int y1, const float z1,
                         const CImg<t>& texture,
                         const int tx0, const int ty0, const int tx1, const int ty1,
                         const float opacity=1.0f, const unsigned int pattern=~0U, const bool init_hatch=true) {
      if (!is_empty() && z0>0 && z1>0) {
        if (!texture || texture.dim<dim)
          throw CImgArgumentException("CImg<%s>::draw_line() : Specified texture (%u,%u,%u,%u,%p) is not a valid argument.",
                                      pixel_type(),texture.width,texture.height,texture.depth,texture.dim,texture.data);
        if (is_overlapping(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.0f/z0, Z1 = 1.0f/z1,
	  dz = Z1-Z0, dtx = Tx1-Tx0, dty = Ty1-Ty0,
          tnx0 = Tx0, tnx1 = Tx1, tny0 = Ty0, tny1 = Ty1, nz0 = Z0, nz1 = Z1,
	  &zleft = xdir?nz0:nz1, &txleft = xdir?tnx0:tnx1, &tyleft = xdir?tny0:tny1,
          &zright = xdir?nz1:nz0, &txright = xdir?tnx1:tnx0, &tyright = xdir?tny1:tny0,
          &zup = ydir?nz0:nz1, &txup = ydir?tnx0:tnx1, &tyup = ydir?tny0:tny1,
          &zdown = ydir?nz1:nz0, &txdown = ydir?tnx1:tnx0, &tydown = ydir?tny1:tny0;

	if (xright<0 || xleft>=dimx()) return *this;
        if (xleft<0) {
	  const int D = xright-xleft;
	  yleft-=xleft*(yright-yleft)/D;
	  zleft-=xleft*(zright-zleft)/D;
	  txleft-=xleft*(txright-txleft)/D;
	  tyleft-=xleft*(tyright-tyleft)/D;
	  xleft = 0;
	}
        if (xright>=dimx()) {
	  const int d = xright-dimx(), D = xright-xleft;
	  yright-=d*(yright-yleft)/D;
	  zright-=d*(zright-zleft)/D;
	  txright-=d*(txright-txleft)/D;
	  tyright-=d*(tyright-tyleft)/D;
	  xright = dimx()-1;
	}
	if (ydown<0 || yup>=dimy()) return *this;
        if (yup<0) {
	  const int D = ydown-yup;
	  xup-=yup*(xdown-xup)/D;
	  zup-=yup*(zdown-zup)/D;
	  txup-=yup*(txdown-txup)/D;
	  tyup-=yup*(tydown-tyup)/D;
	  yup = 0;
	}
        if (ydown>=dimy()) {
	  const int d = ydown-dimy(), D = ydown-yup;
	  xdown-=d*(xdown-xup)/D;
	  zdown-=d*(zdown-zup)/D;
	  txdown-=d*(txdown-txup)/D;
	  tydown-=d*(tydown-tyup)/D;
	  ydown = dimy()-1;
	}
	T *ptrd0 = ptr(nx0,ny0,0,0);
	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) {
	    T *ptrd = ptrd0;
	    if (pattern&hatch) {
	      const float z = Z0 + x*dz/ndx, tx = Tx0 + x*dtx/ndx, ty = Ty0 + x*dty/ndx;
	      cimg_forV(*this,k) { *ptrd = texture((int)(tx/z),(int)(ty/z),0,k); ptrd+=wh; }
	    }
	    hatch>>=1; if (!hatch) hatch = ~0U-(~0U>>1);
	    ptrd0+=offx; if ((error-=dy)<0) { ptrd0+=offy; error+=dx; }
	  } else for (int error=dx>>1, x=0; x<=dx; ++x) {
	    T *ptrd = ptrd0;
	    const float z = Z0 + x*dz/ndx, tx = Tx0 + x*dtx/ndx, ty = Ty0 + x*dty/ndx;
	    cimg_forV(*this,k) { *ptrd = texture((int)(tx/z),(int)(ty/z),0,k); ptrd+=wh; }
	    ptrd0+=offx; if ((error-=dy)<0) { ptrd0+=offy; error+=dx; }
	  }
	} else {
          const float nopacity = cimg::abs(opacity), copacity=1-cimg::max(opacity,0.0f);
	  if (~pattern) for (int error=dx>>1, x=0; x<=dx; ++x) {
	    T *ptrd = ptrd0;
	    if (pattern&hatch) {
	      const float z = Z0 + x*dz/ndx, tx = Tx0 + x*dtx/ndx, ty = Ty0 + x*dty/ndx;
	      cimg_forV(*this,k) { const T c = (T)texture((int)(tx/z),(int)(ty/z),0,k); *ptrd = (T)(c*nopacity + *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 float z = Z0 + x*dz/ndx, tx = Tx0 + x*dtx/ndx, ty = Ty0 + x*dty/ndx;
	    cimg_forV(*this,k) { const T c = (T)texture((int)(tx/z),(int)(ty/z),0,k); *ptrd = (T)(c*nopacity + *ptrd*copacity); ptrd+=wh; }
	    ptrd0+=offx; if ((error-=dy)<0) { ptrd0+=offy; error+=dx; }
	  }
	}
      }
      return *this;
    }

    // Inner routine for drawing set of consecutive lines with generic type for coordinates.
    template<typename t, typename tc>
      CImg<T>& _draw_line(const t& points, const tc *const color,
			  const float opacity, const unsigned int pattern, const bool init_hatch,
			  const unsigned int W, const unsigned int H) {
      bool ninit_hatch = init_hatch;
      if (points && W>1 && H>1) {
        if (H==2) {
          const int x0 = (int)points(0,0), y0 = (int)points(0,1);
          int ox = x0, oy = y0;
          for (unsigned int i=1; i<W; ++i) {
            const int x = (int)points(i,0), y = (int)points(i,1);
            draw_line(ox,oy,x,y,color,opacity,pattern,ninit_hatch);
            ninit_hatch = false;
            ox = x; oy = y;
          }
        } else {
          const int x0 = (int)points(0,0), y0 = (int)points(0,1), z0 = (int)points(0,2);
          int ox = x0, oy = y0, oz = z0;
          for (unsigned int i=1; i<W; ++i) {
            const int x = (int)points(i,0), y = (int)points(i,1), z = (int)points(i,2);
            draw_line(ox,oy,oz,x,y,z,color,opacity,pattern,ninit_hatch);
            ninit_hatch = false;
            ox = x; oy = y; oz = z;
          }
        }
      }
      return *this;
    }

    //! Draw a set of consecutive colored lines in the instance image.
    /**
       \param points Coordinates of vertices, stored as a list of vectors.
       \param color Pointer to \c dimv() consecutive values of type \c T, defining the drawing color.
       \param opacity Drawing opacity (optional).
       \param pattern An integer whose bits describe the line pattern (optional).
       \param init_hatch If set to true, init hatch motif.
       \note
       - This function uses several call to the single CImg::draw_line() procedure,
       depending on the vectors size in \p points.
       \par Example:
       \code
       CImg<unsigned char> img(100,100,1,3,0);
       const unsigned char color[] = { 255,128,64 };
       CImgList<int> points;
       points.insert(CImg<int>::vector(0,0)).
             .insert(CImg<int>::vector(70,10)).
             .insert(CImg<int>::vector(80,60)).
             .insert(CImg<int>::vector(10,90));
       img.draw_line(points,color);
       \endcode
       \sa CImg::draw_line().
    **/
    template<typename t, typename tc>
      CImg<T>& draw_line(const CImgList<t>& points, const tc *const color,
			 const float opacity=1.0f, const unsigned int pattern=~0U, const bool init_hatch=true) {
      unsigned int H = ~0U; cimglist_for(points,p) H = cimg::min(H,(unsigned int)(points[p].size()));
      return _draw_line(points,color,opacity,pattern,init_hatch,points.size,H);
    }

    template<typename t, typename tc>
      CImg<T>& draw_line(const CImgList<t>& points, const CImg<tc>& color,
                         const float opacity=1.0f, const unsigned int pattern=~0U, const bool init_hatch=true) {
      return draw_line(points,color.data,opacity,pattern,init_hatch);
    }

    //! Draw a set of consecutive colored lines in the instance image.
    /**
       \note
       - Similar to the previous function, where the N vertex coordinates are stored as a Nx2 or Nx3 image
       (sequence of vectors aligned along the x-axis).
    **/
    template<typename t, typename tc>
      CImg<T>& draw_line(const CImg<t>& points, const tc *const color,
                         const float opacity=1.0f, const unsigned int pattern=~0U, const bool init_hatch=true) {
      return _draw_line(points,color,opacity,pattern,init_hatch,points.width,points.height);
    }

    template<typename t, typename tc>
      CImg<T>& draw_line(const CImg<t>& points, const CImg<tc>& color,
                         const float opacity=1.0f, const unsigned int pattern=~0U, const bool init_hatch=true) {
      return draw_line(points,color.data,opacity,pattern,init_hatch);
    }

    // Inner routine for a drawing filled polygon with generic type for coordinates.
    template<typename t, typename tc>
      CImg<T>& _draw_polygon(const t& points, const tc *const color, const float opacity, const unsigned int N) {
      if (!is_empty() && N>2) {
        if (!color) throw CImgArgumentException("CImg<%s>::draw_polygon() : Specified color is (null).",pixel_type());
        _draw_scanline(color,opacity);
        int xmin = (int)(~0U>>1), xmax = 0, ymin = (int)(~0U>>1), ymax = 0;
        { for (unsigned int p = 0; p<N; ++p) {
          const int x = (int)points(p,0), y = (int)points(p,1);
          if (x<xmin) xmin = x;
          if (x>xmax) xmax = x;
          if (y<ymin) ymin = y;
          if (y>ymax) ymax = y;
        }}
        if (xmax<0 || xmin>=dimx() || ymax<0 || ymin>=dimx()) return *this;
        const unsigned int
          nymin = ymin<0?0:(unsigned int)ymin,
          nymax = ymax>=dimy()?height-1:(unsigned int)ymax,
          dy = 1 + nymax - nymin;
        typedef typename cimg::last<T,int>::type cint;
        CImg<cint> X(1+2*N,dy,1,1,0), tmp;
        int cx = (int)points(0,0), cy = (int)points(0,1);

        for (unsigned int cp = 0, p = 0; p<N; ++p) {
          const unsigned int np = (p!=N-1)?p+1:0, ap = (np!=N-1)?np+1:0;
          const int
            nx = (int)points(np,0), ny = (int)points(np,1), ay = (int)points(ap,1),
            y0 = cy-nymin, y1 = ny-nymin;
          if (y0!=y1) {
            const int countermin = ((ny<ay && cy<ny) || (ny>ay && cy>ny))?1:0;
            for (int x = cx, y = y0, _sx = 1, _sy = 1,
                   _dx = nx>cx?nx-cx:((_sx=-1),cx-nx),
                   _dy = y1>y0?y1-y0:((_sy=-1),y0-y1),
                   _counter = ((_dx-=_dy?_dy*(_dx/_dy):0),_dy),
                   _err = _dx>>1,
                   _rx = _dy?(nx-cx)/_dy:0;
                 _counter>=countermin;
                 --_counter, y+=_sy, x+=_rx + ((_err-=_dx)<0?_err+=_dy,_sx:0))
              if (y>=0 && y<(int)dy) X(++X(0,y),y) = x;
            cp = np; cx = nx; cy = ny;
          } else {
            const int pp = (cp?cp-1:N-1), py = (int)points(pp,1);
            if ((cy>py && ay>cy) || (cy<py && ay<cy)) X(++X(0,y0),y0) = nx;
            if (cy!=ay) { cp = np; cx = nx; cy = ny; }
          }
        }
        for (int y = 0; y<(int)dy; ++y) {
          tmp.assign(X.ptr(1,y),X(0,y),1,1,1,true).sort();
          for (int i = 1; i<=X(0,y); ) {
            const int xb = X(i++,y), xe = X(i++,y);
            _draw_scanline(xb,xe,nymin+y,color,opacity);
          }
        }
      }
      return *this;
    }

    //! Draw a filled polygon in the instance image.
    template<typename t, typename tc>
      CImg<T>& draw_polygon(const CImgList<t>& points, const tc *const color, const float opacity=1.0f) {
      return _draw_polygon(points,color,opacity,points.size);
    }

    template<typename t, typename tc>
      CImg<T>& draw_polygon(const CImgList<t>& points, const CImg<tc>& color, const float opacity=1.0f) {
      return draw_polygon(points,color.data,opacity);
    }

    //! Draw a filled polygon in the instance image.
    template<typename t, typename tc>
      CImg<T>& draw_polygon(const CImg<t>& points, const tc *const color, const float opacity=1.0f) {
      return _draw_polygon(points,color,opacity,points.width);
    }

    template<typename t, typename tc>
      CImg<T>& draw_polygon(const CImg<t>& points, const CImg<tc>& color, const float opacity=1.0f) {
      return draw_polygon(points,color.data,opacity);
    }

    // Inner routine for drawing an outlined polygon with generic point coordinates.
    template<typename t, typename tc>
      CImg<T>& _draw_polygon(const t& points, const tc *const color,
			     const float opacity, const unsigned int pattern,
			     const unsigned int W, const unsigned int H) {
      bool ninit_hatch = true;
      if (points && W>2 && H>1) {
        if (H==2) {
          const int x0 = (int)points(0,0), y0 = (int)points(0,1);
          int ox = x0, oy = y0;
          for (unsigned int i=1; i<W; ++i) {
            const int x = (int)points(i,0), y = (int)points(i,1);
            draw_line(ox,oy,x,y,color,opacity,pattern,ninit_hatch);
            ninit_hatch = false;
            ox = x; oy = y;
          }
          draw_line(ox,oy,x0,y0,color,opacity,pattern,false);
        } else {
          const int x0 = (int)points(0,0), y0 = (int)points(0,1), z0 = (int)points(0,2);
          int ox = x0, oy = y0, oz = z0;
          for (unsigned int i=1; i<W; ++i) {
            const int x = (int)points(i,0), y = (int)points(i,1), z = (int)points(i,2);
            draw_line(ox,oy,oz,x,y,z,color,opacity,pattern,ninit_hatch);
            ninit_hatch = false;
            ox = x; oy = y; oz = z;
          }
          draw_line(ox,oy,oz,x0,y0,z0,color,opacity,pattern,false);
        }
      }
      return *this;
    }

    // Draw a polygon outline.
    template<typename t, typename tc>
      CImg<T>& draw_polygon(const CImgList<t>& points, const tc *const color,
			    const float opacity, const unsigned int pattern) {
      unsigned int H = ~0U; cimglist_for(points,p) H = cimg::min(H,(unsigned int)(points[p].size()));
      return _draw_polygon(points,color,opacity,pattern,points.size,H);
    }

    template<typename t, typename tc>
      CImg<T>& draw_polygon(const CImgList<t>& points, const CImg<tc>& color,
			    const float opacity, const unsigned int pattern) {
      return draw_polygon(points,color.data,opacity,pattern);
    }

    // Draw a polygon outline.
    template<typename t, typename tc>
      CImg<T>& draw_polygon(const CImg<t>& points, const tc *const color,
			    const float opacity, const unsigned int pattern) {
      return _draw_polygon(points,color,opacity,pattern,points.width,points.height);
    }

    template<typename t, typename tc>
      CImg<T>& draw_polygon(const CImg<t>& points, const CImg<tc>& color,
                            const float opacity, const unsigned int pattern) {
      return draw_polygon(points,color.data,opacity,pattern);
    }

    //! Draw a cubic spline curve in the instance image.
    /**
       \param x0 X-coordinate of the starting curve point
       \param y0 Y-coordinate of the starting curve point
       \param u0 X-coordinate of the starting velocity
       \param v0 Y-coordinate of the starting velocity
       \param x1 X-coordinate of the ending curve point
       \param y1 Y-coordinate of the ending curve point
       \param u1 X-coordinate of the ending velocity
       \param v1 Y-coordinate of the ending velocity
       \param color Pointer to \c dimv() consecutive values of type \c T, defining the drawing color.
       \param precision Curve drawing precision (optional).
       \param opacity Drawing opacity (optional).
       \param pattern An integer whose bits describe the line pattern (optional).
       \param init_hatch If \c true, init hatch motif.
       \note
       - The curve is a 2D cubic Bezier spline, from the set of specified starting/ending points
       and corresponding velocity vectors.
       - The spline is drawn as a serie of connected segments. The \p precision parameter sets the
       average number of pixels in each drawn segment.
       - A cubic Bezier curve is sometimes defined by a set of 4 points { (\p x0,\p y0), (\p xa,\p ya), (\p xb,\p yb), (\p x1,\p y1) }
       where (\p x0,\p y0) is the starting point, (\p x1,\p y1) is the ending point and (\p xa,\p ya), (\p xb,\p yb) are two
       \e control points.
       The starting and ending velocities (\p u0,\p v0) and (\p u1,\p v1) can be deduced easily from the control points as
       \p u0 = (\p xa - \p x0), \p v0 = (\p ya - \p y0), \p u1 = (\p x1 - \p xb) and \p v1 = (\p y1 - \p yb).
       \par Example:
       \code
       CImg<unsigned char> img(100,100,1,3,0);
       const unsigned char color[] = { 255,255,255 };
       img.draw_spline(30,30,0,100,90,40,0,-100,color);
       \endcode
    **/
    template<typename tc>
      CImg<T>& draw_spline(const int x0, const int y0, const float u0, const float v0,
                           const int x1, const int y1, const float u1, const float v1,
                           const tc *const color,
                           const float precision=4.0f, const float opacity=1.0f, const unsigned int pattern=~0U,
                           const bool init_hatch=true) {
      if (!is_empty()) {
        if (!color) throw CImgArgumentException("CImg<%s>::draw_spline() : Specified color is (null)",pixel_type());
        bool ninit_hatch = init_hatch;
        const float
          dx = (float)(x1-x0),
          dy = (float)(y1-y0),
  	  dmax = cimg::max(cimg::abs(dx),cimg::abs(dy)),
          ax = -2*dx + u0 + u1,
          bx = 3*dx - 2*u0 - u1,
          ay = -2*dy + v0 + v1,
          by = 3*dy - 2*v0 - v1,
          xprecision = dmax>0?precision/dmax:1.0f,
          tmax = 1.0f + (dmax>0?xprecision:0.0f);
        int ox = x0, oy = y0;
        for (float t=0.0f; t<tmax; t+=xprecision) {
	  const float
	    t2 = t*t,
	    t3 = t2*t;
          const int
            nx = (int)(ax*t3 + bx*t2 + u0*t + x0),
            ny = (int)(ay*t3 + by*t2 + v0*t + y0);
          draw_line(ox,oy,nx,ny,color,opacity,pattern,ninit_hatch);
	  ninit_hatch = false;
          ox = nx; oy = ny;
        }
      }
      return *this;
    }

    template<typename tc>
      CImg<T>& draw_spline(const int x0, const int y0, const float u0, const float v0,
                           const int x1, const int y1, const float u1, const float v1,
                           const CImg<tc>& color,
                           const float precision=4.0f, const float opacity=1.0f, const unsigned int pattern=~0U,
                           const bool init_hatch=true) {
      return draw_spline(x0,y0,u0,v0,x1,y1,u1,v1,color.data,precision,opacity,pattern,init_hatch);
    }

    //! Draw a cubic spline curve in the instance image (for volumetric images).
    /**
       \note
       - Similar to CImg::draw_spline() for a 3D spline in a volumetric image.
    **/
    template<typename tc>
      CImg<T>& draw_spline(const int x0, const int y0, const int z0, const float u0, const float v0, const float w0,
                           const int x1, const int y1, const int z1, const float u1, const float v1, const float w1,
                           const tc *const color,
                           const float precision=4.0f, const float opacity=1.0f, const unsigned int pattern=~0U,
                           const bool init_hatch=true) {
      if (!is_empty()) {
        if (!color) throw CImgArgumentException("CImg<%s>::draw_spline() : Specified color is (null)",pixel_type());
        bool ninit_hatch = init_hatch;
        const float
          dx = (float)(x1-x0),
          dy = (float)(y1-y0),
          dz = (float)(z1-z0),
	  dmax = cimg::max(cimg::abs(dx),cimg::abs(dy),cimg::abs(dz)),
          ax = -2*dx + u0 + u1,
          bx = 3*dx - 2*u0 - u1,
          ay = -2*dy + v0 + v1,
          by = 3*dy - 2*v0 - v1,
          az = -2*dz + w0 + w1,
          bz = 3*dz - 2*w0 - w1,
          xprecision = dmax>0?precision/dmax:1.0f,
          tmax = 1.0f + (dmax>0?xprecision:0.0f);
        int ox = x0, oy = y0, oz = z0;
        for (float t=0.0f; t<tmax; t+=xprecision) {
	  const float
	    t2 = t*t,
	    t3 = t2*t;
          const int
            nx = (int)(ax*t3 + bx*t2 + u0*t + x0),
            ny = (int)(ay*t3 + by*t2 + v0*t + y0),
            nz = (int)(az*t3 + bz*t2 + w0*t + z0);
          draw_line(ox,oy,oz,nx,ny,nz,color,opacity,pattern,ninit_hatch);
	  ninit_hatch = false;
          ox = nx; oy = ny; oz = nz;
        }
      }
      return *this;
    }

    template<typename tc>
      CImg<T>& draw_spline(const int x0, const int y0, const int z0, const float u0, const float v0, const float w0,
                           const int x1, const int y1, const int z1, const float u1, const float v1, const float w1,
                           const CImg<tc>& color,
                           const float precision=4.0f, const float opacity=1.0f, const unsigned int pattern=~0U,
                           const bool init_hatch=true) {
      return draw_spline(x0,y0,z0,u0,v0,w0,x1,y1,z1,u1,v1,w1,color.data,precision,opacity,pattern,init_hatch);
    }

    //! Draw a cubic spline curve in the instance image.
    /**
       \param x0 X-coordinate of the starting curve point
       \param y0 Y-coordinate of the starting curve point
       \param u0 X-coordinate of the starting velocity
       \param v0 Y-coordinate of the starting velocity
       \param x1 X-coordinate of the ending curve point
       \param y1 Y-coordinate of the ending curve point
       \param u1 X-coordinate of the ending velocity
       \param v1 Y-coordinate of the ending velocity
       \param texture Texture image defining line pixel colors.
       \param tx0 X-coordinate of the starting texture point.
       \param ty0 Y-coordinate of the starting texture point.
       \param tx1 X-coordinate of the ending texture point.
       \param ty1 Y-coordinate of the ending texture point.
       \param precision Curve drawing precision (optional).
       \param opacity Drawing opacity (optional).
       \param pattern An integer whose bits describe the line pattern (optional).
       \param init_hatch if \c true, reinit hatch motif.
    **/
    template<typename t>
      CImg<T>& draw_spline(const int x0, const int y0, const float u0, const float v0,
                           const int x1, const int y1, const float u1, const float v1,
                           const CImg<t>& texture,
                           const int tx0, const int ty0, const int tx1, const int ty1,
                           const float precision=4.0f, const float opacity=1.0f, const unsigned int pattern=~0U,
                           const bool init_hatch=true) {
      if (!is_empty()) {
        if (!texture || texture.dim<dim)
          throw CImgArgumentException("CImg<%s>::draw_line() : Specified texture (%u,%u,%u,%u,%p) is not a valid argument.",
                                      pixel_type(),texture.width,texture.height,texture.depth,texture.dim,texture.data);
        if (is_overlapping(texture)) return draw_spline(x0,y0,u0,v0,x1,y1,u1,v1,+texture,tx0,ty0,tx1,ty1,precision,opacity,pattern,init_hatch);
        bool ninit_hatch = true;
        const float
          dx = (float)(x1-x0),
          dy = (float)(y1-y0),
	  dmax = cimg::max(cimg::abs(dx),cimg::abs(dy)),
          ax = -2*dx + u0 + u1,
          bx = 3*dx - 2*u0 - u1,
          ay = -2*dy + v0 + v1,
          by = 3*dy - 2*v0 - v1,
          xprecision = dmax>0?precision/dmax:1.0f,
          tmax = 1.0f + (dmax>0?xprecision:0.0f);
        int ox = x0, oy = y0, otx = tx0, oty = ty0;
        for (float t1=0.0f; t1<tmax; t1+=xprecision) {
	  const float
	    t2 = t1*t1,
	    t3 = t2*t1;
          const int
            nx = (int)(ax*t3 + bx*t2 + u0*t1 + x0),
            ny = (int)(ay*t3 + by*t2 + v0*t1 + y0),
	    ntx = tx0 + (int)((tx1-tx0)*t1/tmax),
	    nty = ty0 + (int)((ty1-ty0)*t1/tmax);
          draw_line(ox,oy,nx,ny,texture,otx,oty,ntx,nty,opacity,pattern,ninit_hatch);
	  ninit_hatch = false;
          ox = nx; oy = ny; otx = ntx; oty = nty;
        }
      }
      return *this;
    }

    // Draw a set of connected spline curves in the instance image (inner routines).
    template<typename tp, typename tt, typename tc>
      CImg<T>& _draw_spline(const tp& points, const tt& tangents,
                            const tc *const color, const bool close_set,
                            const float precision, const float opacity, const unsigned int pattern, const bool init_hatch,
                            const unsigned int W, const unsigned int H) {
      bool ninit_hatch = init_hatch;
      if (points && tangents && W>1 && H>1) {
        if (H==2) {
          const int x0 = (int)points(0,0), y0 = (int)points(0,1);
          const float u0 = (float)tangents(0,0), v0 = (float)tangents(0,1);
          int ox = x0, oy = y0;
          float ou = u0, ov = v0;
          for (unsigned int i=1; i<W; ++i) {
            const int x = (int)points(i,0), y = (int)points(i,1);
            const float u = (float)tangents(i,0), v = (float)tangents(i,1);
            draw_spline(ox,oy,ou,ov,x,y,u,v,color,precision,opacity,pattern,ninit_hatch);
            ninit_hatch = false;
            ox = x; oy = y; ou = u; ov = v;
          }
          if (close_set) draw_spline(ox,oy,ou,ov,x0,y0,u0,v0,color,precision,opacity,pattern,false);
        } else {
          const int x0 = (int)points(0,0), y0 = (int)points(0,1), z0 = (int)points(0,2);
          const float u0 = (float)tangents(0,0), v0 = (float)tangents(0,1), w0 = (float)tangents(0,2);
          int ox = x0, oy = y0, oz = z0;
          float ou = u0, ov = v0, ow = w0;
          for (unsigned int i=1; i<W; ++i) {
            const int x = (int)points(i,0), y = (int)points(i,1), z = (int)points(i,2);
            const float u = (float)tangents(i,0), v = (float)tangents(i,1), w = (float)tangents(i,2);
            draw_spline(ox,oy,oz,ou,ov,ow,x,y,z,u,v,w,color,opacity,pattern,ninit_hatch);
            ninit_hatch = false;
            ox = x; oy = y; oz = z; ou = u; ov = v; ow = w;
          }
          if (close_set) draw_spline(ox,oy,oz,ou,ov,ow,x0,y0,z0,u0,v0,w0,color,precision,opacity,pattern,false);
        }
      }
      return *this;
    }

    template<typename tp, typename tc>
      CImg<T>& _draw_spline(const tp& points,
                            const tc *const color, const bool close_set,
                            const float precision, const float opacity, const unsigned int pattern, const bool init_hatch,
                            const unsigned int W, const unsigned int H) {
      typedef typename cimg::superset<T,float>::type ftype;
      CImg<ftype> tangents;
      if (points && W>1 && H>1) {
        if (H==2) {
          tangents.assign(W,H);
          for (unsigned int p=0; p<W; ++p) {
            const unsigned int
              p0 = close_set?(p+W-1)%W:(p?p-1:0),
              p1 = close_set?(p+1)%W:(p+1<W?p+1:p);
            const float
              x  = (float)points(p,0),
              y  = (float)points(p,1),
              x0 = (float)points(p0,0),
              y0 = (float)points(p0,1),
              x1 = (float)points(p1,0),
              y1 = (float)points(p1,1),
              u0 = x-x0,
              v0 = y-y0,
              n0 = 1e-8f + (float)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) = (ftype)(fact*u/n);
            tangents(p,1) = (ftype)(fact*v/n);
          }
        } else {
          tangents.assign(W,H);
          for (unsigned int p=0; p<W; ++p) {
            const unsigned int
              p0 = close_set?(p+W-1)%W:(p?p-1:0),
              p1 = close_set?(p+1)%W:(p+1<W?p+1:p);
            const float
              x  = (float)points(p,0),
              y  = (float)points(p,1),
              z  = (float)points(p,2),
              x0 = (float)points(p0,0),
              y0 = (float)points(p0,1),
              z0 = (float)points(p0,2),
              x1 = (float)points(p1,0),
              y1 = (float)points(p1,1),
              z1 = (float)points(p1,2),
              u0 = x-x0,
              v0 = y-y0,
              w0 = z-z0,
              n0 = 1e-8f + (float)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) = (ftype)(fact*u/n);
            tangents(p,1) = (ftype)(fact*v/n);
            tangents(p,2) = (ftype)(fact*w/n);
          }
        }
        _draw_spline(points,tangents,color,close_set,precision,opacity,pattern,init_hatch,W,H);
      }
      return *this;
    }

    //! Draw a set of consecutive colored splines in the instance image.
    template<typename tp, typename tt, typename tc>
      CImg<T>& draw_spline(const CImgList<tp>& points, const CImgList<tt>& tangents,
                           const tc *const color, const bool close_set=false,
                           const float precision=4.0f, const float opacity=1.0f,
                           const unsigned int pattern=~0U, const bool init_hatch=true) {
      unsigned int H = ~0U; cimglist_for(points,p) H = cimg::min(H,(unsigned int)(points[p].size()),(unsigned int)(tangents[p].size()));
      return _draw_spline(points,tangents,color,close_set,precision,opacity,pattern,init_hatch,points.size,H);
    }

    template<typename tp, typename tt, typename tc>
      CImg<T>& draw_spline(const CImgList<tp>& points, const CImgList<tt>& tangents,
                           const CImg<tc>& color, const bool close_set=false,
                           const float precision=4.0f, const float opacity=1.0f,
                           const unsigned int pattern=~0U, const bool init_hatch=true) {
      return draw_spline(points,tangents,color.data,close_set,precision,opacity,pattern,init_hatch);
    }

    //! Draw a set of consecutive colored splines in the instance image.
    template<typename tp, typename tt, typename tc>
      CImg<T>& draw_spline(const CImg<tp>& points, const CImg<tt>& tangents,
                           const tc *const color, const bool close_set=false,
                           const float precision=4.0f, const float opacity=1.0f,
                           const unsigned int pattern=~0U, const bool init_hatch=true) {
      return _draw_spline(points,tangents,color,close_set,precision,opacity,pattern,init_hatch,points.width,points.height);
    }

    template<typename tp, typename tt, typename tc>
      CImg<T>& draw_spline(const CImg<tp>& points, const CImg<tt>& tangents,
                           const CImg<tc>& color, const bool close_set=false,
                           const float precision=4.0f, const float opacity=1.0f,
                           const unsigned int pattern=~0U, const bool init_hatch=true) {
      return draw_spline(points,tangents,color.data,close_set,precision,opacity,pattern,init_hatch);
    }

    //! Draw a set of consecutive colored splines in the instance image.
    template<typename t, typename tc>
      CImg<T>& draw_spline(const CImgList<t>& points, const tc *const color,
                           const bool close_set=false, const float precision=4.0f, const float opacity=1.0f,
                           const unsigned int pattern=~0U, const bool init_hatch=true) {
      unsigned int H = ~0U;
      cimglist_for(points,p) {
        const unsigned int s = points[p].size();
	if (s<H) H = s;
      }
      return _draw_spline(points,color,close_set,precision,opacity,pattern,init_hatch,points.size,H);
    }

    template<typename t, typename tc>
      CImg<T>& draw_spline(const CImgList<t>& points, CImg<tc>& color,
                           const bool close_set=false, const float precision=4.0f, const float opacity=1.0f,
                           const unsigned int pattern=~0U, const bool init_hatch=true) {
      return draw_spline(points,color.data,close_set,precision,opacity,pattern,init_hatch);
    }

    //! Draw a set of consecutive colored lines in the instance image.
    template<typename t, typename tc>
      CImg<T>& draw_spline(const CImg<t>& points, const tc *const color,
                           const bool close_set=false, const float precision=4.0f, const float opacity=1.0f,
                           const unsigned int pattern=~0U, const bool init_hatch=true) {
      return _draw_spline(points,color,close_set,precision,opacity,pattern,init_hatch,points.width,points.height);
    }

    template<typename t, typename tc>
      CImg<T>& draw_spline(const CImg<t>& points, const CImg<tc>& color,
                           const bool close_set=false, const float precision=4.0f, const float opacity=1.0f,
                           const unsigned int pattern=~0U, const bool init_hatch=true) {
      return draw_spline(points,color.data,close_set,precision,opacity,pattern,init_hatch);
    }

    //! Draw a colored arrow in the instance image.
    /**
       \param x0 X-coordinate of the starting arrow point (tail).
       \param y0 Y-coordinate of the starting arrow point (tail).
       \param x1 X-coordinate of the ending arrow point (head).
       \param y1 Y-coordinate of the ending arrow point (head).
       \param color Pointer to \c dimv() consecutive values of type \c T, defining the drawing color.
       \param angle Aperture angle of the arrow head (optional).
       \param length Length of the arrow head. If negative, describes a percentage of the arrow length (optional).
       \param opacity Drawing opacity (optional).
       \param pattern An integer whose bits describe the line pattern (optional).
       \note
       - Clipping is supported.
    **/
    template<typename tc>
      CImg<T>& draw_arrow(const int x0, const int y0, const int x1, const int y1,
                          const tc *const color,
                          const float angle=30, const float length=-10,
                          const float opacity=1.0f, const unsigned int pattern=~0U) {
      if (!is_empty()) {
        const float u = (float)(x0-x1), v = (float)(y0-y1), sq = u*u+v*v,
          deg = (float)(angle*cimg::valuePI/180), ang = (sq>0)?(float)std::atan2(v,u):0.0f,
          l = (length>=0)?length:-length*(float)std::sqrt(sq)/100;
        if (sq>0) {
          const double cl = std::cos(ang-deg), sl = std::sin(ang-deg), cr = std::cos(ang+deg), sr = 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;
    }

    template<typename tc>
      CImg<T>& draw_arrow(const int x0, const int y0, const int x1, const int y1,
                          const CImg<tc>& color,
                          const float angle=30, const float length=-10,
                          const float opacity=1.0f, const unsigned int pattern=~0U) {
      return draw_arrow(x0,y0,x1,y1,color.data,angle,length,opacity,pattern);
    }

    //! Draw a sprite image in the instance image.
    /**
       \param sprite Sprite image.
       \param x0 X-coordinate of the sprite position.
       \param y0 Y-coordinate of the sprite position.
       \param z0 Z-coordinate of the sprite position.
       \param v0 V-coordinate of the sprite position.
       \param opacity Drawing opacity (optional).
       \note
       - Clipping is supported.
    **/
    template<typename t>
      CImg<T>& draw_image(const CImg<t>& sprite,
                          const int x0, const int y0=0, const int z0=0, const int v0=0,
                          const float opacity=1.0f) {
      if (!is_empty()) {
        if (!sprite)
          throw CImgArgumentException("CImg<%s>::draw_image() : Specified sprite image (%u,%u,%u,%u,%p) is empty.",
                                      pixel_type(),sprite.width,sprite.height,sprite.depth,sprite.dim,sprite.data);
        if (is_overlapping(sprite)) return draw_image(+sprite,x0,y0,z0,v0,opacity);
        const bool bx=(x0<0), by=(y0<0), bz=(z0<0), bv=(v0<0);
        const int
          lX = sprite.dimx() - (x0+sprite.dimx()>dimx()?x0+sprite.dimx()-dimx():0) + (bx?x0:0),
          lY = sprite.dimy() - (y0+sprite.dimy()>dimy()?y0+sprite.dimy()-dimy():0) + (by?y0:0),
          lZ = sprite.dimz() - (z0+sprite.dimz()>dimz()?z0+sprite.dimz()-dimz():0) + (bz?z0:0),
          lV = sprite.dimv() - (v0+sprite.dimv()>dimv()?v0+sprite.dimv()-dimv():0) + (bv?v0:0);
        const t
          *ptrs = sprite.data -
          (bx?x0:0) -
          (by?y0*sprite.dimx():0) -
          (bz?z0*sprite.dimx()*sprite.dimy():0) -
          (bv?v0*sprite.dimx()*sprite.dimy()*sprite.dimz():0);
        const unsigned int
          offX = width-lX,                soffX = sprite.width-lX,
          offY = width*(height-lY),       soffY = sprite.width*(sprite.height-lY),
          offZ = width*height*(depth-lZ), soffZ = sprite.width*sprite.height*(sprite.depth-lZ);
        const float nopacity = cimg::abs(opacity), copacity = 1-cimg::max(opacity,0.0f);
        T *ptrd = ptr(x0<0?0:x0,y0<0?0:y0,z0<0?0:z0,v0<0?0:v0);
        if (lX>0 && lY>0 && lZ>0 && lV>0)
          for (int v=0; v<lV; ++v) {
            for (int z=0; z<lZ; ++z) {
              for (int y=0; y<lY; ++y) {
                if (opacity>=1) for (int x=0; x<lX; ++x) *(ptrd++) = (T)*(ptrs++);
                else for (int x=0; x<lX; ++x) { *ptrd = (T)(nopacity*(*(ptrs++)) + *ptrd*copacity); ++ptrd; }
                ptrd+=offX; ptrs+=soffX;
              }
              ptrd+=offY; ptrs+=soffY;
            }
            ptrd+=offZ; ptrs+=soffZ;
          }
      }
      return *this;
    }

#ifndef cimg_use_visualcpp6
    CImg<T>& draw_image(const CImg<T>& sprite,
                        const int x0, const int y0=0, const int z0=0, const int v0=0,
                        const float opacity=1.0f) {
      if (!is_empty()) {
        if (!sprite)
          throw CImgArgumentException("CImg<%s>::draw_image() : Specified sprite image (%u,%u,%u,%u,%p) is empty.",
                                      pixel_type(),sprite.width,sprite.height,sprite.depth,sprite.dim,sprite.data);
        if (is_overlapping(sprite)) return draw_image(+sprite,x0,y0,z0,v0,opacity);
        const bool bx=(x0<0), by=(y0<0), bz=(z0<0), bv=(v0<0);
        const int
          lX = sprite.dimx() - (x0+sprite.dimx()>dimx()?x0+sprite.dimx()-dimx():0) + (bx?x0:0),
          lY = sprite.dimy() - (y0+sprite.dimy()>dimy()?y0+sprite.dimy()-dimy():0) + (by?y0:0),
          lZ = sprite.dimz() - (z0+sprite.dimz()>dimz()?z0+sprite.dimz()-dimz():0) + (bz?z0:0),
          lV = sprite.dimv() - (v0+sprite.dimv()>dimv()?v0+sprite.dimv()-dimv():0) + (bv?v0:0);
        const T
          *ptrs = sprite.data -
          (bx?x0:0) -
          (by?y0*sprite.dimx():0) -
          (bz?z0*sprite.dimx()*sprite.dimy():0) -
          (bv?v0*sprite.dimx()*sprite.dimy()*sprite.dimz():0);
        const unsigned int
          offX = width-lX,                soffX = sprite.width-lX,
          offY = width*(height-lY),       soffY = sprite.width*(sprite.height-lY),
          offZ = width*height*(depth-lZ), soffZ = sprite.width*sprite.height*(sprite.depth-lZ),
          slX = lX*sizeof(T);
        const float nopacity = cimg::abs(opacity), copacity = 1-cimg::max(opacity,0.0f);
        T *ptrd = ptr(x0<0?0:x0,y0<0?0:y0,z0<0?0:z0,v0<0?0:v0);
        if (lX>0 && lY>0 && lZ>0 && lV>0)
          for (int v=0; v<lV; ++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;
    }
#endif

    //! Draw a sprite image in the instance image (masked version).
    /**
       \param sprite Sprite image.
       \param mask Mask image.
       \param x0 X-coordinate of the sprite position in the instance image.
       \param y0 Y-coordinate of the sprite position in the instance image.
       \param z0 Z-coordinate of the sprite position in the instance image.
       \param v0 V-coordinate of the sprite position in the instance image.
       \param mask_valmax Maximum pixel value of the mask image \c mask (optional).
       \param opacity Drawing opacity.
       \note
       - Pixel values of \c mask set the opacity of the corresponding pixels in \c sprite.
       - Clipping is supported.
       - Dimensions along x,y and z of \p sprite and \p mask must be the same.
    **/
    template<typename ti, typename tm>
      CImg<T>& draw_image(const CImg<ti>& sprite, const CImg<tm>& mask,
                          const int x0, const int y0=0, const int z0=0, const int v0=0,
                          const float mask_valmax=1.0f, const float opacity=1.0f) {
      if (!is_empty()) {
        if (!sprite)
          throw CImgArgumentException("CImg<%s>::draw_image() : Specified sprite image (%u,%u,%u,%u,%p) is empty.",
                                      pixel_type(),sprite.width,sprite.height,sprite.depth,sprite.dim,sprite.data);
        if (!mask)
          throw CImgArgumentException("CImg<%s>::draw_image() : Specified mask image (%u,%u,%u,%u,%p) is empty.",
                                      pixel_type(),mask.width,mask.height,mask.depth,mask.dim,mask.data);
        if (is_overlapping(sprite)) return draw_image(+sprite,mask,x0,y0,z0,v0,mask_valmax,opacity);
        if (is_overlapping(mask))   return draw_image(sprite,+mask,x0,y0,z0,v0,mask_valmax,opacity);
        if (mask.width!=sprite.width || mask.height!=sprite.height || mask.depth!=sprite.depth)
          throw CImgArgumentException("CImg<%s>::draw_image() : Mask dimension is (%u,%u,%u,%u), while sprite is (%u,%u,%u,%u)",
                                      pixel_type(),mask.width,mask.height,mask.depth,mask.dim,sprite.width,sprite.height,sprite.depth,sprite.dim);
        const bool bx=(x0<0), by=(y0<0), bz=(z0<0), bv=(v0<0);
        const int
          lX = sprite.dimx() - (x0+sprite.dimx()>dimx()?x0+sprite.dimx()-dimx():0) + (bx?x0:0),
          lY = sprite.dimy() - (y0+sprite.dimy()>dimy()?y0+sprite.dimy()-dimy():0) + (by?y0:0),
          lZ = sprite.dimz() - (z0+sprite.dimz()>dimz()?z0+sprite.dimz()-dimz():0) + (bz?z0:0),
          lV = sprite.dimv() - (v0+sprite.dimv()>dimv()?v0+sprite.dimv()-dimv():0) + (bv?v0:0);
        const int
          coff = -(bx?x0:0)-(by?y0*mask.dimx():0)-(bz?z0*mask.dimx()*mask.dimy():0)-(bv?v0*mask.dimx()*mask.dimy()*mask.dimz():0),
          ssize = mask.dimx()*mask.dimy()*mask.dimz();
        const ti *ptrs = sprite.data + coff;
        const tm *ptrm = mask.data   + coff;
        const unsigned int
          offX = width-lX,                soffX = sprite.width-lX,
          offY = width*(height-lY),       soffY = sprite.width*(sprite.height-lY),
          offZ = width*height*(depth-lZ), soffZ = sprite.width*sprite.height*(sprite.depth-lZ);
        T *ptrd = ptr(x0<0?0:x0,y0<0?0:y0,z0<0?0:z0,v0<0?0:v0);
        if (lX>0 && lY>0 && lZ>0 && lV>0)
          for (int v=0; v<lV; ++v) {
            ptrm = mask.data + (ptrm - mask.data)%ssize;
            for (int z=0; z<lZ; ++z) {
              for (int y=0; y<lY; ++y) {
                for (int x=0; x<lX; ++x) {
                  const float mopacity = (float)(*(ptrm++)*opacity),
                    nopacity = cimg::abs(mopacity), copacity = mask_valmax-cimg::max(mopacity,0.0f);
                  *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 a 4D filled rectangle in the instance image, at coordinates (\c x0,\c y0,\c z0,\c v0)-(\c x1,\c y1,\c z1,\c v1).
    /**
       \param x0 X-coordinate of the upper-left rectangle corner.
       \param y0 Y-coordinate of the upper-left rectangle corner.
       \param z0 Z-coordinate of the upper-left rectangle corner.
       \param v0 V-coordinate of the upper-left rectangle corner.
       \param x1 X-coordinate of the lower-right rectangle corner.
       \param y1 Y-coordinate of the lower-right rectangle corner.
       \param z1 Z-coordinate of the lower-right rectangle corner.
       \param v1 V-coordinate of the lower-right rectangle corner.
       \param val Scalar value used to fill the rectangle area.
       \param opacity Drawing opacity (optional).
       \note
       - Clipping is supported.
    **/
    CImg<T>& draw_rectangle(const int x0, const int y0, const int z0, const int v0,
                            const int x1, const int y1, const int z1, const int v1,
                            const T val, const float opacity=1.0f) {
      if (!is_empty()) {
        const bool bx=(x0<x1), by=(y0<y1), bz=(z0<z1), bv=(v0<v1);
        const int nx0=bx?x0:x1, nx1=bx?x1:x0, ny0=by?y0:y1, ny1=by?y1:y0, nz0=bz?z0:z1, nz1=bz?z1:z0, nv0=bv?v0:v1, nv1=bv?v1:v0;
        const int
          lX = (1+nx1-nx0) + (nx1>=dimx()?dimx()-1-nx1:0) + (nx0<0?nx0:0),
          lY = (1+ny1-ny0) + (ny1>=dimy()?dimy()-1-ny1:0) + (ny0<0?ny0:0),
          lZ = (1+nz1-nz0) + (nz1>=dimz()?dimz()-1-nz1:0) + (nz0<0?nz0:0),
          lV = (1+nv1-nv0) + (nv1>=dimv()?dimv()-1-nv1:0) + (nv0<0?nv0:0);
        const unsigned int offX = width-lX, offY = width*(height-lY), offZ = width*height*(depth-lZ);
        const float nopacity = cimg::abs(opacity), copacity = 1-cimg::max(opacity,0.0f);
        T *ptrd = ptr(nx0<0?0:nx0,ny0<0?0:ny0,nz0<0?0:nz0,nv0<0?0:nv0);
        if (lX>0 && lY>0 && lZ>0 && lV>0)
          for (int v=0; v<lV; ++v) {
            for (int z=0; z<lZ; ++z) {
              for (int y=0; y<lY; ++y) {
                if (opacity>=1) {
                  if (sizeof(T)!=1) { for (int x=0; x<lX; ++x) *(ptrd++) = val; ptrd+=offX; }
                  else { std::memset(ptrd,(int)val,lX); ptrd+=width; }
                } else { for (int x=0; x<lX; ++x) { *ptrd = (T)(nopacity*val + *ptrd*copacity); ++ptrd; } ptrd+=offX; }
              }
              ptrd+=offY;
            }
            ptrd+=offZ;
          }
      }
      return *this;
    }

    //! Draw a 3D filled colored rectangle in the instance image, at coordinates (\c x0,\c y0,\c z0)-(\c x1,\c y1,\c z1).
    /**
       \param x0 X-coordinate of the upper-left rectangle corner.
       \param y0 Y-coordinate of the upper-left rectangle corner.
       \param z0 Z-coordinate of the upper-left rectangle corner.
       \param x1 X-coordinate of the lower-right rectangle corner.
       \param y1 Y-coordinate of the lower-right rectangle corner.
       \param z1 Z-coordinate of the lower-right rectangle corner.
       \param color Pointer to \c dimv() consecutive values of type \c T, defining the drawing color.
       \param opacity Drawing opacity (optional).
       \note
       - Clipping is supported.
    **/
    template<typename tc>
      CImg<T>& draw_rectangle(const int x0, const int y0, const int z0,
                              const int x1, const int y1, const int z1,
                              const tc *const color, const float opacity=1.0f) {
      if (!color) throw CImgArgumentException("CImg<%s>::draw_rectangle : specified color is (null)",pixel_type());
      cimg_forV(*this,k) draw_rectangle(x0,y0,z0,k,x1,y1,z1,k,color[k],opacity);
      return *this;
    }

    template<typename tc>
      CImg<T>& draw_rectangle(const int x0, const int y0, const int z0,
                              const int x1, const int y1, const int z1,
                              const CImg<tc>& color, const float opacity=1.0f) {
      return draw_rectangle(x0,y0,z0,x1,y1,z1,color.data,opacity);
    }

    //! Draw a 3D outlined colored rectangle in the instance image.
    template<typename tc>
      CImg<T>& draw_rectangle(const int x0, const int y0, const int z0,
                              const int x1, const int y1, const int z1,
                              const tc *const color, const float opacity, const unsigned int pattern) {
      return draw_line(x0,y0,z0,x1,y0,z0,color,opacity,pattern,true).
	draw_line(x1,y0,z0,x1,y1,z0,color,opacity,pattern,false).
	draw_line(x1,y1,z0,x0,y1,z0,color,opacity,pattern,false).
	draw_line(x0,y1,z0,x0,y0,z0,color,opacity,pattern,false).
	draw_line(x0,y0,z1,x1,y0,z1,color,opacity,pattern,true).
	draw_line(x1,y0,z1,x1,y1,z1,color,opacity,pattern,false).
	draw_line(x1,y1,z1,x0,y1,z1,color,opacity,pattern,false).
	draw_line(x0,y1,z1,x0,y0,z1,color,opacity,pattern,false).
	draw_line(x0,y0,z0,x0,y0,z1,color,opacity,pattern,true).
	draw_line(x1,y0,z0,x1,y0,z1,color,opacity,pattern,true).
	draw_line(x1,y1,z0,x1,y1,z1,color,opacity,pattern,true).
	draw_line(x0,y1,z0,x0,y1,z1,color,opacity,pattern,true);
    }

    template<typename tc>
      CImg<T>& draw_rectangle(const int x0, const int y0, const int z0,
                              const int x1, const int y1, const int z1,
                              const CImg<tc>& color, const float opacity, const unsigned int pattern) {
      return draw_rectangle(x0,y0,z0,x1,y1,z1,color.data,opacity,pattern);
    }

    //! Draw a 2D filled colored rectangle in the instance image, at coordinates (\c x0,\c y0)-(\c x1,\c y1).
    /**
       \param x0 X-coordinate of the upper-left rectangle corner.
       \param y0 Y-coordinate of the upper-left rectangle corner.
       \param x1 X-coordinate of the lower-right rectangle corner.
       \param y1 Y-coordinate of the lower-right rectangle corner.
       \param color Pointer to \c dimv() consecutive values of type \c T, defining the drawing color.
       \param opacity Drawing opacity (optional).
       \note
       - Clipping is supported.
    **/
    template<typename tc>
      CImg<T>& draw_rectangle(const int x0, const int y0, const int x1, const int y1,
                              const tc *const color, const float opacity=1.0f) {
      return draw_rectangle(x0,y0,0,x1,y1,depth-1,color,opacity);
    }

    template<typename tc>
      CImg<T>& draw_rectangle(const int x0, const int y0, const int x1, const int y1,
                              const CImg<tc>& color, const float opacity=1.0f) {
      return draw_rectangle(x0,y0,x1,y1,color.data,opacity);
    }

    //! Draw a 2D outlined colored rectangle.
    template<typename tc>
      CImg<T>& draw_rectangle(const int x0, const int y0, const int x1, const int y1,
                              const tc *const color, const float opacity, const unsigned int pattern) {
      return draw_line(x0,y0,x1,y0,color,opacity,pattern,true).
	draw_line(x1,y0,x1,y1,color,opacity,pattern,false).
	draw_line(x1,y1,x0,y1,color,opacity,pattern,false).
	draw_line(x0,y1,x0,y0,color,opacity,pattern,false);
    }

    template<typename tc>
      CImg<T>& draw_rectangle(const int x0, const int y0, const int x1, const int y1,
                              const CImg<tc>& color, const float opacity, const unsigned int pattern) {
      return draw_rectangle(x0,y0,x1,y1,color.data,opacity,pattern);
    }

    // Inner macro for drawing triangles.
#define _cimg_for_triangle1(img,xl,xr,y,x0,y0,x1,y1,x2,y2) \
        for (int y = y0<0?0:y0, \
               xr = y0>=0?x0:(x0-y0*(x2-x0)/(y2-y0)), \
               xl = y1>=0?(y0>=0?(y0==y1?x1:x0):(x0-y0*(x1-x0)/(y1-y0))):(x1-y1*(x2-x1)/(y2-y1)), \
               _sxn=1, \
               _sxr=1, \
               _sxl=1, \
               _dxn = x2>x1?x2-x1:(_sxn=-1,x1-x2), \
               _dxr = x2>x0?x2-x0:(_sxr=-1,x0-x2), \
               _dxl = x1>x0?x1-x0:(_sxl=-1,x0-x1), \
               _dyn = y2-y1, \
               _dyr = y2-y0, \
               _dyl = y1-y0, \
               _counter = (_dxn-=_dyn?_dyn*(_dxn/_dyn):0, \
                           _dxr-=_dyr?_dyr*(_dxr/_dyr):0, \
                           _dxl-=_dyl?_dyl*(_dxl/_dyl):0, \
                           cimg::min((int)(img).height-y-1,y2-y)), \
               _errn = _dyn/2, \
               _errr = _dyr/2, \
               _errl = _dyl/2, \
               _rxn = _dyn?(x2-x1)/_dyn:0, \
               _rxr = _dyr?(x2-x0)/_dyr:0, \
               _rxl = (y0!=y1 && y1>0)?(_dyl?(x1-x0)/_dyl:0): \
                                       (_errl=_errn, _dxl=_dxn, _dyl=_dyn, _sxl=_sxn, _rxn); \
             _counter>=0; --_counter, ++y, \
               xr+=_rxr+((_errr-=_dxr)<0?_errr+=_dyr,_sxr:0), \
               xl+=(y!=y1)?_rxl+((_errl-=_dxl)<0?(_errl+=_dyl,_sxl):0): \
                           (_errl=_errn, _dxl=_dxn, _dyl=_dyn, _sxl=_sxn, _rxl=_rxn, x1-xl))

#define _cimg_for_triangle2(img,xl,cl,xr,cr,y,x0,y0,c0,x1,y1,c1,x2,y2,c2) \
        for (int y = y0<0?0:y0, \
               xr = y0>=0?x0:(x0-y0*(x2-x0)/(y2-y0)), \
               cr = y0>=0?c0:(c0-y0*(c2-c0)/(y2-y0)), \
               xl = y1>=0?(y0>=0?(y0==y1?x1:x0):(x0-y0*(x1-x0)/(y1-y0))):(x1-y1*(x2-x1)/(y2-y1)), \
               cl = y1>=0?(y0>=0?(y0==y1?c1:c0):(c0-y0*(c1-c0)/(y1-y0))):(c1-y1*(c2-c1)/(y2-y1)), \
               _sxn=1, _scn=1, \
               _sxr=1, _scr=1, \
               _sxl=1, _scl=1, \
               _dxn = x2>x1?x2-x1:(_sxn=-1,x1-x2), \
               _dxr = x2>x0?x2-x0:(_sxr=-1,x0-x2), \
               _dxl = x1>x0?x1-x0:(_sxl=-1,x0-x1), \
               _dcn = c2>c1?c2-c1:(_scn=-1,c1-c2), \
               _dcr = c2>c0?c2-c0:(_scr=-1,c0-c2), \
               _dcl = c1>c0?c1-c0:(_scl=-1,c0-c1), \
               _dyn = y2-y1, \
               _dyr = y2-y0, \
               _dyl = y1-y0, \
               _counter =(_dxn-=_dyn?_dyn*(_dxn/_dyn):0, \
                          _dxr-=_dyr?_dyr*(_dxr/_dyr):0, \
                          _dxl-=_dyl?_dyl*(_dxl/_dyl):0, \
                          _dcn-=_dyn?_dyn*(_dcn/_dyn):0, \
                          _dcr-=_dyr?_dyr*(_dcr/_dyr):0, \
                          _dcl-=_dyl?_dyl*(_dcl/_dyl):0, \
                          cimg::min((int)(img).height-y-1,y2-y)), \
               _errn = _dyn/2, _errcn = _errn, \
               _errr = _dyr/2, _errcr = _errr, \
               _errl = _dyl/2, _errcl = _errl, \
               _rxn = _dyn?(x2-x1)/_dyn:0, \
               _rcn = _dyn?(c2-c1)/_dyn:0, \
               _rxr = _dyr?(x2-x0)/_dyr:0, \
               _rcr = _dyr?(c2-c0)/_dyr:0, \
               _rxl = (y0!=y1 && y1>0)?(_dyl?(x1-x0)/_dyl:0): \
                                       (_errl=_errn, _dxl=_dxn, _dyl=_dyn, _sxl=_sxn, _rxn), \
               _rcl = (y0!=y1 && y1>0)?(_dyl?(c1-c0)/_dyl:0): \
                                       (_errcl=_errcn, _dcl=_dcn, _dyl=_dyn, _scl=_scn, _rcn ); \
             _counter>=0; --_counter, ++y, \
               xr+=_rxr+((_errr-=_dxr)<0?_errr+=_dyr,_sxr:0), \
               cr+=_rcr+((_errcr-=_dcr)<0?_errcr+=_dyr,_scr:0), \
               xl+=(y!=y1)?(cl+=_rcl+((_errcl-=_dcl)<0?(_errcl+=_dyl,_scl):0), \
      	                   _rxl+((_errl-=_dxl)<0?(_errl+=_dyl,_sxl):0)): \
               (_errcl=_errcn, _dcl=_dcn, _dyl=_dyn, _scl=_scn, _rcl=_rcn, cl=c1, \
                _errl=_errn, _dxl=_dxn, _dyl=_dyn, _sxl=_sxn, _rxl=_rxn, x1-xl))

#define _cimg_for_triangle3(img,xl,txl,tyl,xr,txr,tyr,y,x0,y0,tx0,ty0,x1,y1,tx1,ty1,x2,y2,tx2,ty2) \
        for (int y = y0<0?0:y0, \
               xr = y0>=0?x0:(x0-y0*(x2-x0)/(y2-y0)), \
               txr = y0>=0?tx0:(tx0-y0*(tx2-tx0)/(y2-y0)), \
               tyr = y0>=0?ty0:(ty0-y0*(ty2-ty0)/(y2-y0)), \
               xl = y1>=0?(y0>=0?(y0==y1?x1:x0):(x0-y0*(x1-x0)/(y1-y0))):(x1-y1*(x2-x1)/(y2-y1)), \
               txl = y1>=0?(y0>=0?(y0==y1?tx1:tx0):(tx0-y0*(tx1-tx0)/(y1-y0))):(tx1-y1*(tx2-tx1)/(y2-y1)), \
               tyl = y1>=0?(y0>=0?(y0==y1?ty1:ty0):(ty0-y0*(ty1-ty0)/(y1-y0))):(ty1-y1*(ty2-ty1)/(y2-y1)), \
               _sxn=1, _stxn=1, _styn=1, \
               _sxr=1, _stxr=1, _styr=1, \
               _sxl=1, _stxl=1, _styl=1, \
               _dxn = x2>x1?x2-x1:(_sxn=-1,x1-x2), \
               _dxr = x2>x0?x2-x0:(_sxr=-1,x0-x2), \
               _dxl = x1>x0?x1-x0:(_sxl=-1,x0-x1), \
               _dtxn = tx2>tx1?tx2-tx1:(_stxn=-1,tx1-tx2), \
               _dtxr = tx2>tx0?tx2-tx0:(_stxr=-1,tx0-tx2), \
               _dtxl = tx1>tx0?tx1-tx0:(_stxl=-1,tx0-tx1), \
               _dtyn = ty2>ty1?ty2-ty1:(_styn=-1,ty1-ty2), \
               _dtyr = ty2>ty0?ty2-ty0:(_styr=-1,ty0-ty2), \
               _dtyl = ty1>ty0?ty1-ty0:(_styl=-1,ty0-ty1), \
               _dyn = y2-y1, \
               _dyr = y2-y0, \
               _dyl = y1-y0, \
               _counter =(_dxn-=_dyn?_dyn*(_dxn/_dyn):0, \
                          _dxr-=_dyr?_dyr*(_dxr/_dyr):0, \
                          _dxl-=_dyl?_dyl*(_dxl/_dyl):0, \
                          _dtxn-=_dyn?_dyn*(_dtxn/_dyn):0, \
                          _dtxr-=_dyr?_dyr*(_dtxr/_dyr):0, \
                          _dtxl-=_dyl?_dyl*(_dtxl/_dyl):0, \
                          _dtyn-=_dyn?_dyn*(_dtyn/_dyn):0, \
                          _dtyr-=_dyr?_dyr*(_dtyr/_dyr):0, \
                          _dtyl-=_dyl?_dyl*(_dtyl/_dyl):0, \
                          cimg::min((int)(img).height-y-1,y2-y)), \
               _errn = _dyn/2, _errtxn = _errn, _errtyn = _errn, \
               _errr = _dyr/2, _errtxr = _errr, _errtyr = _errr, \
               _errl = _dyl/2, _errtxl = _errl, _errtyl = _errl, \
               _rxn = _dyn?(x2-x1)/_dyn:0, \
               _rtxn = _dyn?(tx2-tx1)/_dyn:0, \
               _rtyn = _dyn?(ty2-ty1)/_dyn:0, \
               _rxr = _dyr?(x2-x0)/_dyr:0, \
               _rtxr = _dyr?(tx2-tx0)/_dyr:0, \
               _rtyr = _dyr?(ty2-ty0)/_dyr:0, \
               _rxl = (y0!=y1 && y1>0)?(_dyl?(x1-x0)/_dyl:0): \
                                       (_errl=_errn, _dxl=_dxn, _dyl=_dyn, _sxl=_sxn, _rxn), \
               _rtxl = (y0!=y1 && y1>0)?(_dyl?(tx1-tx0)/_dyl:0): \
                                       (_errtxl=_errtxn, _dtxl=_dtxn, _dyl=_dyn, _stxl=_stxn, _rtxn ), \
               _rtyl = (y0!=y1 && y1>0)?(_dyl?(ty1-ty0)/_dyl:0): \
                                       (_errtyl=_errtyn, _dtyl=_dtyn, _dyl=_dyn, _styl=_styn, _rtyn ); \
             _counter>=0; --_counter, ++y, \
               xr+=_rxr+((_errr-=_dxr)<0?_errr+=_dyr,_sxr:0), \
               txr+=_rtxr+((_errtxr-=_dtxr)<0?_errtxr+=_dyr,_stxr:0), \
               tyr+=_rtyr+((_errtyr-=_dtyr)<0?_errtyr+=_dyr,_styr:0), \
               xl+=(y!=y1)?(txl+=_rtxl+((_errtxl-=_dtxl)<0?(_errtxl+=_dyl,_stxl):0), \
                            tyl+=_rtyl+((_errtyl-=_dtyl)<0?(_errtyl+=_dyl,_styl):0), \
                           _rxl+((_errl-=_dxl)<0?(_errl+=_dyl,_sxl):0)): \
               (_errtxl=_errtxn, _dtxl=_dtxn, _dyl=_dyn, _stxl=_stxn, _rtxl=_rtxn, txl=tx1, \
                _errtyl=_errtyn, _dtyl=_dtyn, _dyl=_dyn, _styl=_styn, _rtyl=_rtyn, tyl=ty1,\
                _errl=_errn, _dxl=_dxn, _dyl=_dyn, _sxl=_sxn, _rxl=_rxn, x1-xl))

#define _cimg_for_triangle4(img,xl,cl,txl,tyl,xr,cr,txr,tyr,y,x0,y0,c0,tx0,ty0,x1,y1,c1,tx1,ty1,x2,y2,c2,tx2,ty2) \
        for (int y = y0<0?0:y0, \
               xr = y0>=0?x0:(x0-y0*(x2-x0)/(y2-y0)), \
               cr = y0>=0?c0:(c0-y0*(c2-c0)/(y2-y0)), \
               txr = y0>=0?tx0:(tx0-y0*(tx2-tx0)/(y2-y0)), \
               tyr = y0>=0?ty0:(ty0-y0*(ty2-ty0)/(y2-y0)), \
               xl = y1>=0?(y0>=0?(y0==y1?x1:x0):(x0-y0*(x1-x0)/(y1-y0))):(x1-y1*(x2-x1)/(y2-y1)), \
               cl = y1>=0?(y0>=0?(y0==y1?c1:c0):(c0-y0*(c1-c0)/(y1-y0))):(c1-y1*(c2-c1)/(y2-y1)), \
               txl = y1>=0?(y0>=0?(y0==y1?tx1:tx0):(tx0-y0*(tx1-tx0)/(y1-y0))):(tx1-y1*(tx2-tx1)/(y2-y1)), \
               tyl = y1>=0?(y0>=0?(y0==y1?ty1:ty0):(ty0-y0*(ty1-ty0)/(y1-y0))):(ty1-y1*(ty2-ty1)/(y2-y1)), \
               _sxn=1, _scn=1, _stxn=1, _styn=1, \
               _sxr=1, _scr=1, _stxr=1, _styr=1, \
               _sxl=1, _scl=1, _stxl=1, _styl=1, \
               _dxn = x2>x1?x2-x1:(_sxn=-1,x1-x2), \
               _dxr = x2>x0?x2-x0:(_sxr=-1,x0-x2), \
               _dxl = x1>x0?x1-x0:(_sxl=-1,x0-x1), \
               _dcn = c2>c1?c2-c1:(_scn=-1,c1-c2), \
               _dcr = c2>c0?c2-c0:(_scr=-1,c0-c2), \
               _dcl = c1>c0?c1-c0:(_scl=-1,c0-c1), \
               _dtxn = tx2>tx1?tx2-tx1:(_stxn=-1,tx1-tx2), \
               _dtxr = tx2>tx0?tx2-tx0:(_stxr=-1,tx0-tx2), \
               _dtxl = tx1>tx0?tx1-tx0:(_stxl=-1,tx0-tx1), \
               _dtyn = ty2>ty1?ty2-ty1:(_styn=-1,ty1-ty2), \
               _dtyr = ty2>ty0?ty2-ty0:(_styr=-1,ty0-ty2), \
               _dtyl = ty1>ty0?ty1-ty0:(_styl=-1,ty0-ty1), \
               _dyn = y2-y1, \
               _dyr = y2-y0, \
               _dyl = y1-y0, \
               _counter =(_dxn-=_dyn?_dyn*(_dxn/_dyn):0, \
                          _dxr-=_dyr?_dyr*(_dxr/_dyr):0, \
                          _dxl-=_dyl?_dyl*(_dxl/_dyl):0, \
                          _dcn-=_dyn?_dyn*(_dcn/_dyn):0, \
                          _dcr-=_dyr?_dyr*(_dcr/_dyr):0, \
                          _dcl-=_dyl?_dyl*(_dcl/_dyl):0, \
                          _dtxn-=_dyn?_dyn*(_dtxn/_dyn):0, \
                          _dtxr-=_dyr?_dyr*(_dtxr/_dyr):0, \
                          _dtxl-=_dyl?_dyl*(_dtxl/_dyl):0, \
                          _dtyn-=_dyn?_dyn*(_dtyn/_dyn):0, \
                          _dtyr-=_dyr?_dyr*(_dtyr/_dyr):0, \
                          _dtyl-=_dyl?_dyl*(_dtyl/_dyl):0, \
                          cimg::min((int)(img).height-y-1,y2-y)), \
               _errn = _dyn/2, _errcn = _errn, _errtxn = _errn, _errtyn = _errn, \
               _errr = _dyr/2, _errcr = _errr, _errtxr = _errr, _errtyr = _errr, \
               _errl = _dyl/2, _errcl = _errl, _errtxl = _errl, _errtyl = _errl, \
               _rxn = _dyn?(x2-x1)/_dyn:0, \
               _rcn = _dyn?(c2-c1)/_dyn:0, \
               _rtxn = _dyn?(tx2-tx1)/_dyn:0, \
               _rtyn = _dyn?(ty2-ty1)/_dyn:0, \
               _rxr = _dyr?(x2-x0)/_dyr:0, \
               _rcr = _dyr?(c2-c0)/_dyr:0, \
               _rtxr = _dyr?(tx2-tx0)/_dyr:0, \
               _rtyr = _dyr?(ty2-ty0)/_dyr:0, \
               _rxl = (y0!=y1 && y1>0)?(_dyl?(x1-x0)/_dyl:0): \
                                       (_errl=_errn, _dxl=_dxn, _dyl=_dyn, _sxl=_sxn, _rxn), \
               _rcl = (y0!=y1 && y1>0)?(_dyl?(c1-c0)/_dyl:0): \
                                       (_errcl=_errcn, _dcl=_dcn, _dyl=_dyn, _scl=_scn, _rcn ), \
               _rtxl = (y0!=y1 && y1>0)?(_dyl?(tx1-tx0)/_dyl:0): \
                                        (_errtxl=_errtxn, _dtxl=_dtxn, _dyl=_dyn, _stxl=_stxn, _rtxn ), \
               _rtyl = (y0!=y1 && y1>0)?(_dyl?(ty1-ty0)/_dyl:0): \
                                        (_errtyl=_errtyn, _dtyl=_dtyn, _dyl=_dyn, _styl=_styn, _rtyn ); \
             _counter>=0; --_counter, ++y, \
               xr+=_rxr+((_errr-=_dxr)<0?_errr+=_dyr,_sxr:0), \
               cr+=_rcr+((_errcr-=_dcr)<0?_errcr+=_dyr,_scr:0), \
               txr+=_rtxr+((_errtxr-=_dtxr)<0?_errtxr+=_dyr,_stxr:0), \
               tyr+=_rtyr+((_errtyr-=_dtyr)<0?_errtyr+=_dyr,_styr:0), \
               xl+=(y!=y1)?(cl+=_rcl+((_errcl-=_dcl)<0?(_errcl+=_dyl,_scl):0), \
                            txl+=_rtxl+((_errtxl-=_dtxl)<0?(_errtxl+=_dyl,_stxl):0), \
                            tyl+=_rtyl+((_errtyl-=_dtyl)<0?(_errtyl+=_dyl,_styl):0), \
                            _rxl+((_errl-=_dxl)<0?(_errl+=_dyl,_sxl):0)): \
               (_errcl=_errcn, _dcl=_dcn, _dyl=_dyn, _scl=_scn, _rcl=_rcn, cl=c1, \
                _errtxl=_errtxn, _dtxl=_dtxn, _dyl=_dyn, _stxl=_stxn, _rtxl=_rtxn, txl=tx1, \
                _errtyl=_errtyn, _dtyl=_dtyn, _dyl=_dyn, _styl=_styn, _rtyl=_rtyn, tyl=ty1, \
                _errl=_errn, _dxl=_dxn, _dyl=_dyn, _sxl=_sxn, _rxl=_rxn, x1-xl))

#define _cimg_for_triangle5(img,xl,txl,tyl,lxl,lyl,xr,txr,tyr,lxr,lyr,y,x0,y0,tx0,ty0,lx0,ly0,x1,y1,tx1,ty1,lx1,ly1,x2,y2,tx2,ty2,lx2,ly2) \
        for (int y = y0<0?0:y0, \
               xr = y0>=0?x0:(x0-y0*(x2-x0)/(y2-y0)), \
               txr = y0>=0?tx0:(tx0-y0*(tx2-tx0)/(y2-y0)), \
               tyr = y0>=0?ty0:(ty0-y0*(ty2-ty0)/(y2-y0)), \
               lxr = y0>=0?lx0:(lx0-y0*(lx2-lx0)/(y2-y0)), \
               lyr = y0>=0?ly0:(ly0-y0*(ly2-ly0)/(y2-y0)), \
               xl = y1>=0?(y0>=0?(y0==y1?x1:x0):(x0-y0*(x1-x0)/(y1-y0))):(x1-y1*(x2-x1)/(y2-y1)), \
               txl = y1>=0?(y0>=0?(y0==y1?tx1:tx0):(tx0-y0*(tx1-tx0)/(y1-y0))):(tx1-y1*(tx2-tx1)/(y2-y1)), \
               tyl = y1>=0?(y0>=0?(y0==y1?ty1:ty0):(ty0-y0*(ty1-ty0)/(y1-y0))):(ty1-y1*(ty2-ty1)/(y2-y1)), \
               lxl = y1>=0?(y0>=0?(y0==y1?lx1:lx0):(lx0-y0*(lx1-lx0)/(y1-y0))):(lx1-y1*(lx2-lx1)/(y2-y1)), \
               lyl = y1>=0?(y0>=0?(y0==y1?ly1:ly0):(ly0-y0*(ly1-ly0)/(y1-y0))):(ly1-y1*(ly2-ly1)/(y2-y1)), \
               _sxn=1, _stxn=1, _styn=1, _slxn=1, _slyn=1, \
               _sxr=1, _stxr=1, _styr=1, _slxr=1, _slyr=1, \
               _sxl=1, _stxl=1, _styl=1, _slxl=1, _slyl=1, \
               _dxn = x2>x1?x2-x1:(_sxn=-1,x1-x2), _dyn = y2-y1, \
               _dxr = x2>x0?x2-x0:(_sxr=-1,x0-x2), _dyr = y2-y0, \
               _dxl = x1>x0?x1-x0:(_sxl=-1,x0-x1), _dyl = y1-y0, \
               _dtxn = tx2>tx1?tx2-tx1:(_stxn=-1,tx1-tx2), \
               _dtxr = tx2>tx0?tx2-tx0:(_stxr=-1,tx0-tx2), \
               _dtxl = tx1>tx0?tx1-tx0:(_stxl=-1,tx0-tx1), \
               _dtyn = ty2>ty1?ty2-ty1:(_styn=-1,ty1-ty2), \
               _dtyr = ty2>ty0?ty2-ty0:(_styr=-1,ty0-ty2), \
               _dtyl = ty1>ty0?ty1-ty0:(_styl=-1,ty0-ty1), \
               _dlxn = lx2>lx1?lx2-lx1:(_slxn=-1,lx1-lx2), \
               _dlxr = lx2>lx0?lx2-lx0:(_slxr=-1,lx0-lx2), \
               _dlxl = lx1>lx0?lx1-lx0:(_slxl=-1,lx0-lx1), \
               _dlyn = ly2>ly1?ly2-ly1:(_slyn=-1,ly1-ly2), \
               _dlyr = ly2>ly0?ly2-ly0:(_slyr=-1,ly0-ly2), \
               _dlyl = ly1>ly0?ly1-ly0:(_slyl=-1,ly0-ly1), \
               _counter =(_dxn-=_dyn?_dyn*(_dxn/_dyn):0, \
                          _dxr-=_dyr?_dyr*(_dxr/_dyr):0, \
                          _dxl-=_dyl?_dyl*(_dxl/_dyl):0, \
                          _dtxn-=_dyn?_dyn*(_dtxn/_dyn):0, \
                          _dtxr-=_dyr?_dyr*(_dtxr/_dyr):0, \
                          _dtxl-=_dyl?_dyl*(_dtxl/_dyl):0, \
                          _dtyn-=_dyn?_dyn*(_dtyn/_dyn):0, \
                          _dtyr-=_dyr?_dyr*(_dtyr/_dyr):0, \
                          _dtyl-=_dyl?_dyl*(_dtyl/_dyl):0, \
                          _dlxn-=_dyn?_dyn*(_dlxn/_dyn):0, \
                          _dlxr-=_dyr?_dyr*(_dlxr/_dyr):0, \
                          _dlxl-=_dyl?_dyl*(_dlxl/_dyl):0, \
                          _dlyn-=_dyn?_dyn*(_dlyn/_dyn):0, \
                          _dlyr-=_dyr?_dyr*(_dlyr/_dyr):0, \
                          _dlyl-=_dyl?_dyl*(_dlyl/_dyl):0, \
                          cimg::min((int)(img).height-y-1,y2-y)), \
               _errn = _dyn/2, _errtxn = _errn, _errtyn = _errn, _errlxn = _errn, _errlyn = _errn, \
               _errr = _dyr/2, _errtxr = _errr, _errtyr = _errr, _errlxr = _errr, _errlyr = _errr, \
               _errl = _dyl/2, _errtxl = _errl, _errtyl = _errl, _errlxl = _errl, _errlyl = _errl, \
               _rxn = _dyn?(x2-x1)/_dyn:0, \
               _rtxn = _dyn?(tx2-tx1)/_dyn:0, \
               _rtyn = _dyn?(ty2-ty1)/_dyn:0, \
               _rlxn = _dyn?(lx2-lx1)/_dyn:0, \
               _rlyn = _dyn?(ly2-ly1)/_dyn:0, \
               _rxr = _dyr?(x2-x0)/_dyr:0, \
               _rtxr = _dyr?(tx2-tx0)/_dyr:0, \
               _rtyr = _dyr?(ty2-ty0)/_dyr:0, \
               _rlxr = _dyr?(lx2-lx0)/_dyr:0, \
               _rlyr = _dyr?(ly2-ly0)/_dyr:0, \
               _rxl = (y0!=y1 && y1>0)?(_dyl?(x1-x0)/_dyl:0): \
                                       (_errl=_errn, _dxl=_dxn, _dyl=_dyn, _sxl=_sxn, _rxn), \
               _rtxl = (y0!=y1 && y1>0)?(_dyl?(tx1-tx0)/_dyl:0): \
                                        (_errtxl=_errtxn, _dtxl=_dtxn, _dyl=_dyn, _stxl=_stxn, _rtxn ), \
               _rtyl = (y0!=y1 && y1>0)?(_dyl?(ty1-ty0)/_dyl:0): \
                                        (_errtyl=_errtyn, _dtyl=_dtyn, _dyl=_dyn, _styl=_styn, _rtyn ), \
               _rlxl = (y0!=y1 && y1>0)?(_dyl?(lx1-lx0)/_dyl:0): \
                                        (_errlxl=_errlxn, _dlxl=_dlxn, _dyl=_dyn, _slxl=_slxn, _rlxn ), \
               _rlyl = (y0!=y1 && y1>0)?(_dyl?(ly1-ly0)/_dyl:0): \
                                        (_errlyl=_errlyn, _dlyl=_dlyn, _dyl=_dyn, _slyl=_slyn, _rlyn ); \
             _counter>=0; --_counter, ++y, \
               xr+=_rxr+((_errr-=_dxr)<0?_errr+=_dyr,_sxr:0), \
               txr+=_rtxr+((_errtxr-=_dtxr)<0?_errtxr+=_dyr,_stxr:0), \
               tyr+=_rtyr+((_errtyr-=_dtyr)<0?_errtyr+=_dyr,_styr:0), \
               lxr+=_rlxr+((_errlxr-=_dlxr)<0?_errlxr+=_dyr,_slxr:0), \
               lyr+=_rlyr+((_errlyr-=_dlyr)<0?_errlyr+=_dyr,_slyr:0), \
               xl+=(y!=y1)?(txl+=_rtxl+((_errtxl-=_dtxl)<0?(_errtxl+=_dyl,_stxl):0), \
                            tyl+=_rtyl+((_errtyl-=_dtyl)<0?(_errtyl+=_dyl,_styl):0), \
                            lxl+=_rlxl+((_errlxl-=_dlxl)<0?(_errlxl+=_dyl,_slxl):0), \
                            lyl+=_rlyl+((_errlyl-=_dlyl)<0?(_errlyl+=_dyl,_slyl):0), \
                            _rxl+((_errl-=_dxl)<0?(_errl+=_dyl,_sxl):0)): \
               (_errtxl=_errtxn, _dtxl=_dtxn, _dyl=_dyn, _stxl=_stxn, _rtxl=_rtxn, txl=tx1, \
                _errtyl=_errtyn, _dtyl=_dtyn, _dyl=_dyn, _styl=_styn, _rtyl=_rtyn, tyl=ty1, \
                _errlxl=_errlxn, _dlxl=_dlxn, _dyl=_dyn, _slxl=_slxn, _rlxl=_rlxn, lxl=lx1, \
                _errlyl=_errlyn, _dlyl=_dlyn, _dyl=_dyn, _slyl=_slyn, _rlyl=_rlyn, lyl=ly1, \
                _errl=_errn, _dxl=_dxn, _dyl=_dyn, _sxl=_sxn, _rxl=_rxn, x1-xl))

    // Draw a colored triangle (inner routine, uses bresenham's algorithm)
    template<typename tc>
      CImg<T>& _draw_triangle(const int x0, const int y0, const int x1, const int y1, const int x2, const int y2,
                              const tc *const color, const float opacity, const float brightness) {
      _draw_scanline(color,opacity);
      const float nbrightness = brightness<0?0:(brightness>2?2:brightness);
      int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2;
      if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1);
      if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2);
      if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2);
      if (ny0<dimy() && ny2>=0) {
        if ((nx1-nx0)*(ny2-ny0)-(nx2-nx0)*(ny1-ny0)<0)
          _cimg_for_triangle1(*this,xl,xr,y,nx0,ny0,nx1,ny1,nx2,ny2) _draw_scanline(xl,xr,y,color,opacity,nbrightness);
        else
          _cimg_for_triangle1(*this,xl,xr,y,nx0,ny0,nx1,ny1,nx2,ny2) _draw_scanline(xr,xl,y,color,opacity,nbrightness);
      }
      return *this;
    }

    //! Draw a 2D filled colored triangle in the instance image.
    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.0f) {
      if (!is_empty()) {
        if (!color) throw CImgArgumentException("CImg<%s>::draw_triangle : Specified color is (null).",pixel_type());
        _draw_triangle(x0,y0,x1,y1,x2,y2,color,opacity,1.0f);
      }
      return *this;
    }

    template<typename tc>
      CImg<T>& draw_triangle(const int x0, const int y0, const int x1, const int y1, const int x2, const int y2,
                             const CImg<tc>& color, const float opacity=1.0f) {
      return draw_triangle(x0,y0,x1,y1,x2,y2,color.data,opacity);
    }

    //! Draw a 2D outlined colored triangle
    template<typename tc>
      CImg<T>& draw_triangle(const int x0, const int y0, const int x1, const int y1, const int x2, const int y2,
                             const tc *const color, const float opacity, const unsigned int pattern) {
      if (!is_empty()) {
        if (!color) throw CImgArgumentException("CImg<%s>::draw_triangle : Specified color is (null).",pixel_type());
        draw_line(x0,y0,x1,y1,color,opacity,pattern,true).
          draw_line(x1,y1,x2,y2,color,opacity,pattern,false).
          draw_line(x2,y2,x0,y0,color,opacity,pattern,false);
      }
      return *this;
    }

    template<typename tc>
      CImg<T>& draw_triangle(const int x0, const int y0, const int x1, const int y1, const int x2, const int y2,
                             const CImg<tc>& color, const float opacity, const unsigned int pattern) {
      return draw_triangle(x0,y0,x1,y1,x2,y2,color.data,opacity,pattern);
    }

    //! Draw a 2D Gouraud-filled triangle in the instance image, at coordinates (\c x0,\c y0)-(\c x1,\c y1)-(\c x2,\c y2).
    /**
       \param x0 = X-coordinate of the first corner in the instance image.
       \param y0 = Y-coordinate of the first corner in the instance image.
       \param x1 = X-coordinate of the second corner in the instance image.
       \param y1 = Y-coordinate of the second corner in the instance image.
       \param x2 = X-coordinate of the third corner in the instance image.
       \param y2 = Y-coordinate of the third corner in the instance image.
       \param color = array of dimv() values of type \c T, defining the global drawing color.
       \param brightness0 = brightness of the first corner (in [0,2]).
       \param brightness1 = brightness of the second corner (in [0,2]).
       \param brightness2 = brightness of the third corner (in [0,2]).
       \param opacity = opacity of the drawing.
       \note Clipping is supported.
    **/
    template<typename tc>
      CImg<T>& draw_triangle(const int x0, const int y0,
                             const int x1, const int y1,
                             const int x2, const int y2,
                             const tc *const color,
                             const float brightness0,
                             const float brightness1,
                             const float brightness2,
                             const float opacity=1.0f) {
      if (!is_empty()) {
        if (!color) throw CImgArgumentException("CImg<%s>::draw_triangle : Specified color is (null).",pixel_type());
        static const T maxval = (T)cimg::min(cimg::type<T>::max(),cimg::type<tc>::max());
        const float nopacity = cimg::abs(opacity), copacity = 1-cimg::max(opacity,0.0f);
        const int whz = width*height*depth, offx = dim*whz-1;
        int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2,
          nc0 = (int)((brightness0<0?0:(brightness0>2?2:brightness0))*256),
          nc1 = (int)((brightness1<0?0:(brightness1>2?2:brightness1))*256),
          nc2 = (int)((brightness2<0?0:(brightness2>2?2:brightness2))*256);
        if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,nc0,nc1);
        if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,nc0,nc2);
        if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,nc1,nc2);
        if (ny0<dimy() && ny2>=0) {
          _cimg_for_triangle2(*this,xleft0,cleft0,xright0,cright0,y,nx0,ny0,nc0,nx1,ny1,nc1,nx2,ny2,nc2) {
            int xleft = xleft0, xright = xright0, cleft = cleft0, cright = cright0;
            if (xright<xleft) cimg::swap(xleft,xright,cleft,cright);
            const int
              dx = xright-xleft,
              dc = cright>cleft?cright-cleft:cleft-cright,
              rc = dx?(cright-cleft)/dx:0,
              sc = cright>cleft?1:-1,
              ndc = dc-(dx?dx*(dc/dx):0);
            int errc = dx>>1;
            if (xleft<0 && dx) cleft-=xleft*(cright-cleft)/dx;
            if (xleft<0) xleft=0;
            if (xright>=dimx()-1) xright=dimx()-1;
            T* ptrd = ptr(xleft,y,0,0);
            if (opacity>=1) for (int x=xleft; x<=xright; ++x) {
              const tc *col = color;
              cimg_forV(*this,k) {
                *ptrd = (T)(cleft<256?cleft**(col++)/256:((512-cleft)**(col++)+(cleft-256)*maxval)/256);
                ptrd+=whz;
              }
              ptrd-=offx;
              cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0);
            } else for (int x=xleft; x<=xright; ++x) {
              const tc *col = color;
              cimg_forV(*this,k) {
                const T val = (T)(cleft<256?cleft**(col++)/256:((512-cleft)**(col++)+(cleft-256)*maxval)/256);
                *ptrd = (T)(nopacity*val + *ptrd*copacity);
                ptrd+=whz;
              }
              ptrd-=offx;
              cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0);
            }
          }
        }
      }
      return *this;
    }

    template<typename tc>
      CImg<T>& draw_triangle(const int x0, const int y0,
                             const int x1, const int y1,
                             const int x2, const int y2,
                             const CImg<tc>& color,
                             const float brightness0,
                             const float brightness1,
                             const float brightness2,
                             const float opacity=1.0f) {
      return draw_triangle(x0,y0,x1,y1,x2,y2,color.data,brightness0,brightness1,brightness2,opacity);
    }

    //! Draw a 2D textured triangle in the instance image, at coordinates (\c x0,\c y0)-(\c x1,\c y1)-(\c x2,\c y2).
    /**
       \param x0 = X-coordinate of the first corner in the instance image.
       \param y0 = Y-coordinate of the first corner in the instance image.
       \param x1 = X-coordinate of the second corner in the instance image.
       \param y1 = Y-coordinate of the second corner in the instance image.
       \param x2 = X-coordinate of the third corner in the instance image.
       \param y2 = Y-coordinate of the third corner in the instance image.
       \param texture = texture image used to fill the triangle.
       \param tx0 = X-coordinate of the first corner in the texture image.
       \param ty0 = Y-coordinate of the first corner in the texture image.
       \param tx1 = X-coordinate of the second corner in the texture image.
       \param ty1 = Y-coordinate of the second corner in the texture image.
       \param tx2 = X-coordinate of the third corner in the texture image.
       \param ty2 = Y-coordinate of the third corner in the texture image.
       \param opacity = opacity of the drawing.
       \param brightness = brightness of the drawing (in [0,2]).
       \note Clipping is supported, but texture coordinates do not support clipping.
    **/
    template<typename t> CImg<T>& draw_triangle(const int x0, const int y0,
                                                const int x1, const int y1,
                                                const int x2, const int y2,
                                                const CImg<t>& texture,
                                                const int tx0, const int ty0,
                                                const int tx1, const int ty1,
                                                const int tx2, const int ty2,
                                                const float opacity=1.0f,
                                                const float brightness=1.0f) {
      if (!is_empty()) {
        if (!texture || texture.dim<dim) throw CImgArgumentException("CImg<%s>::draw_triangle() : Specified texture (%u,%u,%u,%u,%p) is not a valid argument.",
                                                                     pixel_type(),texture.width,texture.height,texture.depth,texture.dim,texture.data);
        if (is_overlapping(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<t>::max());
        const float
          nopacity = cimg::abs(opacity),
          copacity = 1-cimg::max(opacity,0.0f),
          nbrightness = brightness<0?0:(brightness>2?2:brightness);
        const int whz = width*height*depth, twhz = texture.width*texture.height*texture.depth, offx = dim*whz-1;
        int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2,
          ntx0 = tx0, nty0 = ty0, ntx1 = tx1, nty1 = ty1, ntx2 = tx2, nty2 = ty2;
        if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,ntx0,ntx1,nty0,nty1);
        if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,ntx0,ntx2,nty0,nty2);
        if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,ntx1,ntx2,nty1,nty2);
        if (ny0<dimy() && ny2>=0) {
          _cimg_for_triangle3(*this,xleft0,txleft0,tyleft0,xright0,txright0,tyright0,y,
                              nx0,ny0,ntx0,nty0,nx1,ny1,ntx1,nty1,nx2,ny2,ntx2,nty2) {
            int
              xleft = xleft0, xright = xright0,
              txleft = txleft0, txright = txright0,
              tyleft = tyleft0, tyright = tyright0;
            if (xright<xleft) cimg::swap(xleft,xright,txleft,txright,tyleft,tyright);
            const int
              dx = xright-xleft,
              dtx = txright>txleft?txright-txleft:txleft-txright,
              dty = tyright>tyleft?tyright-tyleft:tyleft-tyright,
              rtx = dx?(txright-txleft)/dx:0,
              rty = dx?(tyright-tyleft)/dx:0,
              stx = txright>txleft?1:-1,
              sty = tyright>tyleft?1:-1,
              ndtx = dtx-(dx?dx*(dtx/dx):0),
              ndty = dty-(dx?dx*(dty/dx):0);
            int errtx = dx>>1, errty = errtx;
            if (xleft<0 && dx) {
              txleft-=xleft*(txright-txleft)/dx;
              tyleft-=xleft*(tyright-tyleft)/dx;
            }
            if (xleft<0) xleft=0;
            if (xright>=dimx()-1) xright=dimx()-1;
            T* ptrd = ptr(xleft,y,0,0);
            if (opacity>=1) {
              if (nbrightness==1) for (int x=xleft; x<=xright; ++x) {
                const t *col = texture.ptr(txleft,tyleft);
                cimg_forV(*this,k) {
                  *ptrd = (T)*col;
                  ptrd+=whz; col+=twhz;
                }
                ptrd-=offx;
                txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0);
                tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0);
              } else if (nbrightness<1) for (int x=xleft; x<=xright; ++x) {
                const t *col = texture.ptr(txleft,tyleft);
                cimg_forV(*this,k) {
                  *ptrd = (T)(nbrightness**col);
                  ptrd+=whz; col+=twhz;
                }
                ptrd-=offx;
                txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0);
                tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0);
              } else for (int x=xleft; x<=xright; ++x) {
                const t *col = texture.ptr(txleft,tyleft);
                cimg_forV(*this,k) {
                  *ptrd = (T)((2-nbrightness)**(col++) + (nbrightness-1)*maxval);
                  ptrd+=whz; col+=twhz;
                }
                ptrd-=offx;
                txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0);
                tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0);
              }
            } else {
              if (nbrightness==1) for (int x=xleft; x<=xright; ++x) {
                const t *col = texture.ptr(txleft,tyleft);
                cimg_forV(*this,k) {
                  *ptrd = (T)(nopacity**col + *ptrd*copacity);
                  ptrd+=whz; col+=twhz;
                }
                ptrd-=offx;
                txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0);
                tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0);
              } else if (nbrightness<1) for (int x=xleft; x<=xright; ++x) {
                const t *col = texture.ptr(txleft,tyleft);
                cimg_forV(*this,k) {
                  *ptrd = (T)(nopacity*nbrightness**col + *ptrd*copacity);
                  ptrd+=whz; col+=twhz;
                }
                ptrd-=offx;
                txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0);
                tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0);
              } else for (int x=xleft; x<=xright; ++x) {
                const t *col = texture.ptr(txleft,tyleft);
                cimg_forV(*this,k) {
                  const T val = (T)((2-nbrightness)**(col++) + (nbrightness-1)*maxval);
                  *ptrd = (T)(nopacity*val + *ptrd*copacity);
                  ptrd+=whz; col+=twhz;
                }
                ptrd-=offx;
                txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0);
                tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0);
              }
            }
          }
        }
      }
      return *this;
    }

    //! Draw a textured triangle with perspective correction.
    template<typename t> 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<t>& texture,
                                                const int tx0, const int ty0,
                                                const int tx1, const int ty1,
                                                const int tx2, const int ty2,
                                                const float opacity=1.0f,
                                                const float brightness=1.0f) {
      if (!is_empty() && z0>0 && z1>0 && z2>0) {
        if (!texture || texture.dim<dim) throw CImgArgumentException("CImg<%s>::draw_triangle() : Specified texture (%u,%u,%u,%u,%p) is not a valid argument.",
                                                                     pixel_type(),texture.width,texture.height,texture.depth,texture.dim,texture.data);
        if (is_overlapping(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<t>::max());
        const float
          nopacity = cimg::abs(opacity),
          copacity = 1-cimg::max(opacity,0.0f),
          nbrightness = brightness<0?0:(brightness>2?2:brightness);
        const int whz = width*height*depth, twhz = texture.width*texture.height*texture.depth, offx = dim*whz-1;
        int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2;
        float
          ntx0 = tx0/z0, nty0 = ty0/z0,
          ntx1 = tx1/z1, nty1 = ty1/z1,
          ntx2 = tx2/z2, nty2 = ty2/z2,
          nz0 = 1.0f/z0, nz1 = 1.0f/z1, nz2 = 1.0f/z2;
        if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,ntx0,ntx1,nty0,nty1,nz0,nz1);
        if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,ntx0,ntx2,nty0,nty2,nz0,nz2);
        if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,ntx1,ntx2,nty1,nty2,nz1,nz2);
        if (ny0<dimy() && ny2>=0) {
          float
            ptxl = (ntx1-ntx0)/(ny1-ny0),
            ptxr = (ntx2-ntx0)/(ny2-ny0),
            ptxn = (ntx2-ntx1)/(ny2-ny1),
            ptyl = (nty1-nty0)/(ny1-ny0),
            ptyr = (nty2-nty0)/(ny2-ny0),
            ptyn = (nty2-nty1)/(ny2-ny1),
            pzl = (nz1-nz0)/(ny1-ny0),
            pzr = (nz2-nz0)/(ny2-ny0),
            pzn = (nz2-nz1)/(ny2-ny1),
            zr  = ny0>=0?nz0:(nz0-ny0*(nz2-nz0)/(ny2-ny0)),
            txr = ny0>=0?ntx0:(ntx0-ny0*(ntx2-ntx0)/(ny2-ny0)),
            tyr = ny0>=0?nty0:(nty0-ny0*(nty2-nty0)/(ny2-ny0)),
            zl  = ny1>=0?(ny0>=0?nz0:(nz0-ny0*(nz1-nz0)/(ny1-ny0))):(pzl=pzn,(nz1-ny1*(nz2-nz1)/(ny2-ny1))),
            txl = ny1>=0?(ny0>=0?ntx0:(ntx0-ny0*(ntx1-ntx0)/(ny1-ny0))):(ptxl=ptxn,(ntx1-ny1*(ntx2-ntx1)/(ny2-ny1))),
            tyl = ny1>=0?(ny0>=0?nty0:(nty0-ny0*(nty1-nty0)/(ny1-ny0))):(ptyl=ptyn,(nty1-ny1*(nty2-nty1)/(ny2-ny1)));
          _cimg_for_triangle1(*this,xleft0,xright0,y,nx0,ny0,nx1,ny1,nx2,ny2) {
            if (y==ny1) { zl = nz1; txl = ntx1; tyl = nty1; pzl = pzn; ptxl = ptxn; ptyl = ptyn; }
            int xleft = xleft0, xright = xright0;
            float
              zleft = zl, zright = zr,
              txleft = txl, txright = txr,
              tyleft = tyl, tyright = tyr;
            if (xright<xleft) cimg::swap(xleft,xright,zleft,zright,txleft,txright,tyleft,tyright);
            const int dx = xright-xleft;
            const float
              pentez = (zright-zleft)/dx,
              pentetx = (txright-txleft)/dx,
              pentety = (tyright-tyleft)/dx;
            if (xleft<0 && dx) {
              zleft-=xleft*(zright-zleft)/dx;
              txleft-=xleft*(txright-txleft)/dx;
              tyleft-=xleft*(tyright-tyleft)/dx;
            }
            if (xleft<0) xleft=0;
            if (xright>=dimx()-1) xright=dimx()-1;
            T* ptrd = ptr(xleft,y,0,0);
            if (opacity>=1) {
              if (nbrightness==1) for (int x=xleft; x<=xright; ++x) {
                const float invz = 1.0f/zleft;
                const t *col = texture.ptr((int)(txleft*invz),(int)(tyleft*invz));
                cimg_forV(*this,k) {
                  *ptrd = (T)*col;
                  ptrd+=whz; col+=twhz;
                }
                ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
              } else if (nbrightness<1) for (int x=xleft; x<=xright; ++x) {
                const float invz = 1.0f/zleft;
                const t *col = texture.ptr((int)(txleft*invz),(int)(tyleft*invz));
                cimg_forV(*this,k) {
                  *ptrd = (T)(nbrightness**col);
                  ptrd+=whz; col+=twhz;
                }
                ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
              } else for (int x=xleft; x<=xright; ++x) {
                const float invz = 1.0f/zleft;
                const t *col = texture.ptr((int)(txleft*invz),(int)(tyleft*invz));
                cimg_forV(*this,k) {
                  *ptrd = (T)((2-nbrightness)**(col++) + (nbrightness-1)*maxval);
                  ptrd+=whz; col+=twhz;
                }
                ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
              }
            } else {
              if (nbrightness==1) for (int x=xleft; x<=xright; ++x) {
                const float invz = 1.0f/zleft;
                const t *col = texture.ptr((int)(txleft*invz),(int)(tyleft*invz));
                cimg_forV(*this,k) {
                  *ptrd = (T)(nopacity**col + *ptrd*copacity);
                  ptrd+=whz; col+=twhz;
                }
                ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
              } else if (nbrightness<1) for (int x=xleft; x<=xright; ++x) {
                const float invz = 1.0f/zleft;
                const t *col = texture.ptr((int)(txleft*invz),(int)(tyleft*invz));
                cimg_forV(*this,k) {
                  *ptrd = (T)(nopacity*nbrightness**col + *ptrd*copacity);
                  ptrd+=whz; col+=twhz;
                }
                ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
              } else for (int x=xleft; x<=xright; ++x) {
                const float invz = 1.0f/zleft;
                const t *col = texture.ptr((int)(txleft*invz),(int)(tyleft*invz));
                cimg_forV(*this,k) {
                  const T val = (T)((2-nbrightness)**(col++) + (nbrightness-1)*maxval);
                  *ptrd = (T)(nopacity*val + *ptrd*copacity);
                  ptrd+=whz; col+=twhz;
                }
                ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
              }
            }
            zr+=pzr; txr+=ptxr; tyr+=ptyr; zl+=pzl; txl+=ptxl; tyl+=ptyl;
          }
        }
      }
      return *this;
    }

    //! Draw a 2D phong-shaded triangle in the instance image, at coordinates (\c x0,\c y0)-(\c x1,\c y1)-(\c x2,\c y2).
    /**
       \param x0 = X-coordinate of the first corner in the instance image.
       \param y0 = Y-coordinate of the first corner in the instance image.
       \param x1 = X-coordinate of the second corner in the instance image.
       \param y1 = Y-coordinate of the second corner in the instance image.
       \param x2 = X-coordinate of the third corner in the instance image.
       \param y2 = Y-coordinate of the third corner in the instance image.
       \param color = array of dimv() values of type \c T, defining the global drawing color.
       \param light = light image.
       \param lx0 = X-coordinate of the first corner in the light image.
       \param ly0 = Y-coordinate of the first corner in the light image.
       \param lx1 = X-coordinate of the second corner in the light image.
       \param ly1 = Y-coordinate of the second corner in the light image.
       \param lx2 = X-coordinate of the third corner in the light image.
       \param ly2 = Y-coordinate of the third corner in the light image.
       \param opacity = opacity of the drawing.
       \note Clipping is supported, but texture coordinates do not support clipping.
    **/
    template<typename tc, typename t>
      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<t>& light,
                             const int lx0, const int ly0,
                             const int lx1, const int ly1,
                             const int lx2, const int ly2,
                             const float opacity=1.0f) {
      if (!is_empty()) {
        if (!color) throw CImgArgumentException("CImg<%s>::draw_triangle : Specified color is (null).",pixel_type());
        if (!light) throw CImgArgumentException("CImg<%s>::draw_triangle() : Specified light texture (%u,%u,%u,%u,%p) is empty.",
                                                pixel_type(),light.width,light.height,light.depth,light.dim,light.data);
        if (is_overlapping(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.0f);
        int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2,
          nlx0 = lx0, nly0 = ly0, nlx1 = lx1, nly1 = ly1, nlx2 = lx2, nly2 = ly2;
        const int whz = width*height*depth, offx = dim*whz-1;
        if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,nlx0,nlx1,nly0,nly1);
        if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,nlx0,nlx2,nly0,nly2);
        if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,nlx1,nlx2,nly1,nly2);
        if (ny0<dimy() && ny2>=0) {
          _cimg_for_triangle3(*this,xleft0,lxleft0,lyleft0,xright0,lxright0,lyright0,y,
                              nx0,ny0,nlx0,nly0,nx1,ny1,nlx1,nly1,nx2,ny2,nlx2,nly2) {
            int
              xleft = xleft0, xright = xright0,
              lxleft = lxleft0, lxright = lxright0,
              lyleft = lyleft0, lyright = lyright0;
            if (xright<xleft) cimg::swap(xleft,xright,lxleft,lxright,lyleft,lyright);
            const int
              dx = xright-xleft,
              dlx = lxright>lxleft?lxright-lxleft:lxleft-lxright,
              dly = lyright>lyleft?lyright-lyleft:lyleft-lyright,
              rlx = dx?(lxright-lxleft)/dx:0,
              rly = dx?(lyright-lyleft)/dx:0,
              slx = lxright>lxleft?1:-1,
              sly = lyright>lyleft?1:-1,
              ndlx = dlx-(dx?dx*(dlx/dx):0),
              ndly = dly-(dx?dx*(dly/dx):0);
            int errlx = dx>>1, errly = errlx;
            if (xleft<0 && dx) {
              lxleft-=xleft*(lxright-lxleft)/dx;
              lyleft-=xleft*(lyright-lyleft)/dx;
            }
            if (xleft<0) xleft=0;
            if (xright>=dimx()-1) xright=dimx()-1;
            T* ptrd = ptr(xleft,y,0,0);
            if (opacity>=1) for (int x=xleft; x<=xright; ++x) {
              const t l = light(lxleft,lyleft);
              const tc *col = color;
              cimg_forV(*this,k) {
                *ptrd = (T)(l<1?l**(col++):((2-l)**(col++)+(l-1)*maxval));
                ptrd+=whz;
              }
              ptrd-=offx;
              lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0);
              lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0);
            } else  for (int x=xleft; x<=xright; ++x) {
              const t l = light(lxleft,lyleft);
              const tc *col = color;
              cimg_forV(*this,k) {
                const T val = (T)(l<1?l**(col++):((2-l)**(col++)+(l-1)*maxval));
                *ptrd = (T)(nopacity*val + *ptrd*copacity);
                ptrd+=whz;
              }
              ptrd-=offx;
              lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0);
              lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0);
            }
          }
        }
      }
      return *this;
    }

    template<typename tc, typename t>
      CImg<T>& draw_triangle(const int x0, const int y0,
                             const int x1, const int y1,
                             const int x2, const int y2,
                             const CImg<tc>& color,
                             const CImg<t>& light,
                             const int lx0, const int ly0,
                             const int lx1, const int ly1,
                             const int lx2, const int ly2,
                             const float opacity=1.0f) {
      return draw_triangle(x0,y0,x1,y1,x2,y2,color.data,light,lx0,ly0,lx1,ly1,lx2,ly2,opacity);
    }

    //! Draw a 2D textured triangle with Gouraud-Shading in the instance image, at coordinates (\c x0,\c y0)-(\c x1,\c y1)-(\c x2,\c y2).
    /**
       \param x0 = X-coordinate of the first corner in the instance image.
       \param y0 = Y-coordinate of the first corner in the instance image.
       \param x1 = X-coordinate of the second corner in the instance image.
       \param y1 = Y-coordinate of the second corner in the instance image.
       \param x2 = X-coordinate of the third corner in the instance image.
       \param y2 = Y-coordinate of the third corner in the instance image.
       \param texture = texture image used to fill the triangle.
       \param tx0 = X-coordinate of the first corner in the texture image.
       \param ty0 = Y-coordinate of the first corner in the texture image.
       \param tx1 = X-coordinate of the second corner in the texture image.
       \param ty1 = Y-coordinate of the second corner in the texture image.
       \param tx2 = X-coordinate of the third corner in the texture image.
       \param ty2 = Y-coordinate of the third corner in the texture image.
       \param brightness0 = brightness value of the first corner.
       \param brightness1 = brightness value of the second corner.
       \param brightness2 = brightness value of the third corner.
       \param opacity = opacity of the drawing.
       \note Clipping is supported, but texture coordinates do not support clipping.
    **/
    template<typename t> CImg<T>& draw_triangle(const int x0, const int y0,
                                                const int x1, const int y1,
                                                const int x2, const int y2,
                                                const CImg<t>& 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()) {
        if (!texture || texture.dim<dim) throw CImgArgumentException("CImg<%s>::draw_triangle() : Specified texture (%u,%u,%u,%u,%p) is not a valid argument.",
                                                  pixel_type(),texture.width,texture.height,texture.depth,texture.dim,texture.data);
        if (is_overlapping(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<t>::max());
        const float nopacity = cimg::abs(opacity), copacity = 1-cimg::max(opacity,0.0f);
        const int whz = width*height*depth, twhz = texture.width*texture.height*texture.depth, offx = dim*whz-1;
        int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2,
          ntx0 = tx0, nty0 = ty0, ntx1 = tx1, nty1 = ty1, ntx2 = tx2, nty2 = ty2,
          nc0 = (int)((brightness0<0?0:(brightness0>2?2:brightness0))*256),
          nc1 = (int)((brightness1<0?0:(brightness1>2?2:brightness1))*256),
          nc2 = (int)((brightness2<0?0:(brightness2>2?2:brightness2))*256);
        if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,ntx0,ntx1,nty0,nty1,nc0,nc1);
        if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,ntx0,ntx2,nty0,nty2,nc0,nc2);
        if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,ntx1,ntx2,nty1,nty2,nc1,nc2);
        if (ny0<dimy() && ny2>=0) {
          _cimg_for_triangle4(*this,xleft0,cleft0,txleft0,tyleft0,xright0,cright0,txright0,tyright0,y,
                              nx0,ny0,nc0,ntx0,nty0,nx1,ny1,nc1,ntx1,nty1,nx2,ny2,nc2,ntx2,nty2) {
            int
              xleft = xleft0, xright = xright0,
              cleft = cleft0, cright = cright0,
              txleft = txleft0, txright = txright0,
              tyleft = tyleft0, tyright = tyright0;
            if (xright<xleft) cimg::swap(xleft,xright,cleft,cright,txleft,txright,tyleft,tyright);
            const int
              dx = xright-xleft,
              dc = cright>cleft?cright-cleft:cleft-cright,
              dtx = txright>txleft?txright-txleft:txleft-txright,
              dty = tyright>tyleft?tyright-tyleft:tyleft-tyright,
              rc = dx?(cright-cleft)/dx:0,
              rtx = dx?(txright-txleft)/dx:0,
              rty = dx?(tyright-tyleft)/dx:0,
              sc = cright>cleft?1:-1,
              stx = txright>txleft?1:-1,
              sty = tyright>tyleft?1:-1,
              ndc = dc-(dx?dx*(dc/dx):0),
              ndtx = dtx-(dx?dx*(dtx/dx):0),
              ndty = dty-(dx?dx*(dty/dx):0);
            int errc = dx>>1, errtx = errc, errty = errc;
            if (xleft<0 && dx) {
              cleft-=xleft*(cright-cleft)/dx;
              txleft-=xleft*(txright-txleft)/dx;
              tyleft-=xleft*(tyright-tyleft)/dx;
            }
            if (xleft<0) xleft=0;
            if (xright>=dimx()-1) xright=dimx()-1;
            T* ptrd = ptr(xleft,y,0,0);
            if (opacity>=1) for (int x=xleft; x<=xright; ++x) {
              const t *col = texture.ptr(txleft,tyleft);
              cimg_forV(*this,k) {
                *ptrd = (T)(cleft<256?cleft**col/256:((512-cleft)**col+(cleft-256)*maxval)/256);
                ptrd+=whz; col+=twhz;
              }
              ptrd-=offx;
              cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0);
              txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0);
              tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0);
            } else for (int x=xleft; x<=xright; ++x) {
              const t *col = texture.ptr(txleft,tyleft);
              cimg_forV(*this,k) {
                const T val = (T)(cleft<256?cleft**col/256:((512-cleft)**col+(cleft-256)*maxval)/256);
                *ptrd = (T)(nopacity*val + *ptrd*copacity);
                ptrd+=whz; col+=twhz;
              }
              ptrd-=offx;
              cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0);
              txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0);
              tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0);
            }
          }
        }
      }
      return *this;
    }

    //! Draw a gouraud + textured triangle with perspective correction.
    template<typename t> 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<t>& texture,
                                                const int tx0, const int ty0,
                                                const int tx1, const int ty1,
                                                const int tx2, const int ty2,
                                                const float c0,
                                                const float c1,
                                                const float c2,
                                                const float opacity=1.0f) {
      if (!is_empty() && z0>0 && z1>0 && z2>0) {
        if (!texture || texture.dim<dim) throw CImgArgumentException("CImg<%s>::draw_triangle() : Specified texture (%u,%u,%u,%u,%p) is not a valid argument.",
                                                                     pixel_type(),texture.width,texture.height,texture.depth,texture.dim,texture.data);
        if (is_overlapping(texture)) return draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,+texture,tx0,ty0,tx1,ty1,tx2,ty2,c0,c1,c2,opacity);
        static const T maxval = (T)cimg::min(cimg::type<T>::max(),cimg::type<t>::max());
        const float nopacity = cimg::abs(opacity), copacity = 1-cimg::max(opacity,0.0f);
        const int whz = width*height*depth, twhz = texture.width*texture.height*texture.depth, offx = dim*whz-1;
        int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2,
          nc0 = (int)(c0*256), nc1 = (int)(c1*256), nc2 = (int)(c2*256);
        float
          ntx0 = tx0/z0, nty0 = ty0/z0,
          ntx1 = tx1/z1, nty1 = ty1/z1,
          ntx2 = tx2/z2, nty2 = ty2/z2,
          nz0 = 1.0f/z0, nz1 = 1.0f/z1, nz2 = 1.0f/z2;
        if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,ntx0,ntx1,nty0,nty1,nz0,nz1,nc0,nc1);
        if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,ntx0,ntx2,nty0,nty2,nz0,nz2,nc0,nc2);
        if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,ntx1,ntx2,nty1,nty2,nz1,nz2,nc1,nc2);
        if (ny0<dimy() && ny2>=0) {
          float
            ptxl = (ntx1-ntx0)/(ny1-ny0),
            ptxr = (ntx2-ntx0)/(ny2-ny0),
            ptxn = (ntx2-ntx1)/(ny2-ny1),
            ptyl = (nty1-nty0)/(ny1-ny0),
            ptyr = (nty2-nty0)/(ny2-ny0),
            ptyn = (nty2-nty1)/(ny2-ny1),
            pzl = (nz1-nz0)/(ny1-ny0),
            pzr = (nz2-nz0)/(ny2-ny0),
            pzn = (nz2-nz1)/(ny2-ny1),
            zr  = ny0>=0?nz0:(nz0-ny0*(nz2-nz0)/(ny2-ny0)),
            txr = ny0>=0?ntx0:(ntx0-ny0*(ntx2-ntx0)/(ny2-ny0)),
            tyr = ny0>=0?nty0:(nty0-ny0*(nty2-nty0)/(ny2-ny0)),
            zl  = ny1>=0?(ny0>=0?nz0:(nz0-ny0*(nz1-nz0)/(ny1-ny0))):(pzl=pzn,(nz1-ny1*(nz2-nz1)/(ny2-ny1))),
            txl = ny1>=0?(ny0>=0?ntx0:(ntx0-ny0*(ntx1-ntx0)/(ny1-ny0))):(ptxl=ptxn,(ntx1-ny1*(ntx2-ntx1)/(ny2-ny1))),
            tyl = ny1>=0?(ny0>=0?nty0:(nty0-ny0*(nty1-nty0)/(ny1-ny0))):(ptyl=ptyn,(nty1-ny1*(nty2-nty1)/(ny2-ny1)));
          _cimg_for_triangle2(*this,xleft0,cleft0,xright0,cright0,y,nx0,ny0,nc0,nx1,ny1,nc1,nx2,ny2,nc2) {
            if (y==ny1) { zl = nz1; txl = ntx1; tyl = nty1; pzl = pzn; ptxl = ptxn; ptyl = ptyn; }
            int
              xleft = xleft0, xright = xright0,
              cleft = cleft0, cright = cright0;
            float
              zleft = zl, zright = zr,
              txleft = txl, txright = txr,
              tyleft = tyl, tyright = tyr;
            if (xright<xleft) cimg::swap(xleft,xright,zleft,zright,txleft,txright,tyleft,tyright,cleft,cright);
            const int
              dx = xright-xleft,
              dc = cright>cleft?cright-cleft:cleft-cright,
              rc = dx?(cright-cleft)/dx:0,
              sc = cright>cleft?1:-1,
              ndc = dc-(dx?dx*(dc/dx):0);
            const float
              pentez = (zright-zleft)/dx,
              pentetx = (txright-txleft)/dx,
              pentety = (tyright-tyleft)/dx;
            int errc = dx>>1;
            if (xleft<0 && dx) {
              cleft-=xleft*(cright-cleft)/dx;
              zleft-=xleft*(zright-zleft)/dx;
              txleft-=xleft*(txright-txleft)/dx;
              tyleft-=xleft*(tyright-tyleft)/dx;
            }
            if (xleft<0) xleft=0;
            if (xright>=dimx()-1) xright=dimx()-1;
            T* ptrd = ptr(xleft,y,0,0);
            if (opacity>=1) for (int x=xleft; x<=xright; ++x) {
              const float invz = 1.0f/zleft;
              const t *col = texture.ptr((int)(txleft*invz),(int)(tyleft*invz));
              cimg_forV(*this,k) {
                *ptrd = (T)(cleft<256?cleft**col/256:((512-cleft)**col+(cleft-256)*maxval)/256);
                ptrd+=whz; col+=twhz;
              }
              ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
              cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0);
            } else for (int x=xleft; x<=xright; ++x) {
              const float invz = 1.0f/zleft;
              const t *col = texture.ptr((int)(txleft*invz),(int)(tyleft*invz));
              cimg_forV(*this,k) {
                const T val = (T)(cleft<256?cleft**col/256:((512-cleft)**col+(cleft-256)*maxval)/256);
                *ptrd = (T)(nopacity*val + *ptrd*copacity);
                ptrd+=whz; col+=twhz;
              }
              ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
              cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0);
            }
            zr+=pzr; txr+=ptxr; tyr+=ptyr; zl+=pzl; txl+=ptxl; tyl+=ptyl;
          }
        }
      }
      return *this;
    }

    //! Draw a phong-shaded 2D textured triangle in the instance image, at coordinates (\c x0,\c y0)-(\c x1,\c y1)-(\c x2,\c y2).
    /**
       \param x0 = X-coordinate of the first corner in the instance image.
       \param y0 = Y-coordinate of the first corner in the instance image.
       \param x1 = X-coordinate of the second corner in the instance image.
       \param y1 = Y-coordinate of the second corner in the instance image.
       \param x2 = X-coordinate of the third corner in the instance image.
       \param y2 = Y-coordinate of the third corner in the instance image.
       \param texture = texture image used to fill the triangle.
       \param tx0 = X-coordinate of the first corner in the texture image.
       \param ty0 = Y-coordinate of the first corner in the texture image.
       \param tx1 = X-coordinate of the second corner in the texture image.
       \param ty1 = Y-coordinate of the second corner in the texture image.
       \param tx2 = X-coordinate of the third corner in the texture image.
       \param ty2 = Y-coordinate of the third corner in the texture image.
       \param light = light image.
       \param lx0 = X-coordinate of the first corner in the light image.
       \param ly0 = Y-coordinate of the first corner in the light image.
       \param lx1 = X-coordinate of the second corner in the light image.
       \param ly1 = Y-coordinate of the second corner in the light image.
       \param lx2 = X-coordinate of the third corner in the light image.
       \param ly2 = Y-coordinate of the third corner in the light image.
       \param opacity = opacity of the drawing.
       \note Clipping is supported, but texture coordinates do not support clipping.
    **/
    template<typename t, 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<t>& 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.0f) {
      if (!is_empty()) {
        if (!texture || texture.dim<dim) throw CImgArgumentException("CImg<%s>::draw_triangle() : Specified texture (%u,%u,%u,%u,%p) is not a valid argument.",
                                                                     pixel_type(),texture.width,texture.height,texture.depth,texture.dim,texture.data);
        if (!light) throw CImgArgumentException("CImg<%s>::draw_triangle() : Specified light texture (%u,%u,%u,%u,%p) is empty.",
                                                                     pixel_type(),light.width,light.height,light.depth,light.dim,light.data);
        if (is_overlapping(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_overlapping(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<t>::max());
        const float nopacity = cimg::abs(opacity), copacity = 1-cimg::max(opacity,0.0f);
        const int whz = width*height*depth, twhz = texture.width*texture.height*texture.depth, offx = dim*whz-1;
        int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2,
          ntx0 = tx0, nty0 = ty0, ntx1 = tx1, nty1 = ty1, ntx2 = tx2, nty2 = ty2,
          nlx0 = lx0, nly0 = ly0, nlx1 = lx1, nly1 = ly1, nlx2 = lx2, nly2 = ly2;
        if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,ntx0,ntx1,nty0,nty1,nlx0,nlx1,nly0,nly1);
        if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,ntx0,ntx2,nty0,nty2,nlx0,nlx2,nly0,nly2);
        if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,ntx1,ntx2,nty1,nty2,nlx1,nlx2,nly1,nly2);
        if (ny0<dimy() && ny2>=0) {
          _cimg_for_triangle5(*this,xleft0,lxleft0,lyleft0,txleft0,tyleft0,xright0,lxright0,lyright0,txright0,tyright0,y,
                              nx0,ny0,nlx0,nly0,ntx0,nty0,nx1,ny1,nlx1,nly1,ntx1,nty1,nx2,ny2,nlx2,nly2,ntx2,nty2) {
            int
              xleft = xleft0, xright = xright0,
              lxleft = lxleft0, lxright = lxright0,
              lyleft = lyleft0, lyright = lyright0,
              txleft = txleft0, txright = txright0,
              tyleft = tyleft0, tyright = tyright0;
            if (xright<xleft) cimg::swap(xleft,xright,lxleft,lxright,lyleft,lyright,txleft,txright,tyleft,tyright);
            const int
              dx = xright-xleft,
              dlx = lxright>lxleft?lxright-lxleft:lxleft-lxright,
              dly = lyright>lyleft?lyright-lyleft:lyleft-lyright,
              dtx = txright>txleft?txright-txleft:txleft-txright,
              dty = tyright>tyleft?tyright-tyleft:tyleft-tyright,
              rlx = dx?(lxright-lxleft)/dx:0,
              rly = dx?(lyright-lyleft)/dx:0,
              rtx = dx?(txright-txleft)/dx:0,
              rty = dx?(tyright-tyleft)/dx:0,
              slx = lxright>lxleft?1:-1,
              sly = lyright>lyleft?1:-1,
              stx = txright>txleft?1:-1,
              sty = tyright>tyleft?1:-1,
              ndlx = dlx-(dx?dx*(dlx/dx):0),
              ndly = dly-(dx?dx*(dly/dx):0),
              ndtx = dtx-(dx?dx*(dtx/dx):0),
              ndty = dty-(dx?dx*(dty/dx):0);
            int errlx = dx>>1, errly = errlx, errtx = errlx, errty = errlx;
            if (xleft<0 && dx) {
              lxleft-=xleft*(lxright-lxleft)/dx;
              lyleft-=xleft*(lyright-lyleft)/dx;
              txleft-=xleft*(txright-txleft)/dx;
              tyleft-=xleft*(tyright-tyleft)/dx;
            }
            if (xleft<0) xleft=0;
            if (xright>=dimx()-1) xright=dimx()-1;
            T* ptrd = ptr(xleft,y,0,0);
            if (opacity>=1) for (int x=xleft; x<=xright; ++x) {
              const tl l = light(lxleft,lyleft);
              const t *col = texture.ptr(txleft,tyleft);
              cimg_forV(*this,k) {
                *ptrd = (T)(l<1?l**col:(2-l)**col+(l-1)*maxval);
                ptrd+=whz; col+=twhz;
              }
              ptrd-=offx;
              lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0);
              lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0);
              txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0);
              tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0);
            } else for (int x=xleft; x<=xright; ++x) {
              const tl l = light(lxleft,lyleft);
              const t *col = texture.ptr(txleft,tyleft);
              cimg_forV(*this,k) {
                const T val = (T)(l<1?l**col:(2-l)**col+(l-1)*maxval);
                *ptrd = (T)(nopacity*val + *ptrd*copacity);
                ptrd+=whz; col+=twhz;
              }
              ptrd-=offx;
              lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0);
              lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0);
              txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0);
              tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0);
            }
          }
        }
      }
      return *this;
    }

    //! Draw a phong + textured triangle with perspective correction.
    template<typename t, 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<t>& 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.0f) {
      if (!is_empty() && z0>0 && z1>0 && z2>0) {
        if (!texture || texture.dim<dim) throw CImgArgumentException("CImg<%s>::draw_triangle() : Specified texture (%u,%u,%u,%u,%p) is not a valid argument.",
                                                                     pixel_type(),texture.width,texture.height,texture.depth,texture.dim,texture.data);
        if (!light) throw CImgArgumentException("CImg<%s>::draw_triangle() : Specified light texture (%u,%u,%u,%u,%p) is empty.",
                                                                     pixel_type(),light.width,light.height,light.depth,light.dim,light.data);
        if (is_overlapping(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_overlapping(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<t>::max());
        const float nopacity = cimg::abs(opacity), copacity = 1-cimg::max(opacity,0.0f);
        const int whz = width*height*depth, twhz = texture.width*texture.height*texture.depth, offx = dim*whz-1;
        int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2,
          nlx0 = lx0, nly0 = ly0, nlx1 = lx1, nly1 = ly1, nlx2 = lx2, nly2 = ly2;
        float
          ntx0 = tx0/z0, nty0 = ty0/z0,
          ntx1 = tx1/z1, nty1 = ty1/z1,
          ntx2 = tx2/z2, nty2 = ty2/z2,
          nz0 = 1.0f/z0, nz1 = 1.0f/z1, nz2 = 1.0f/z2;
        if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,ntx0,ntx1,nty0,nty1,nlx0,nlx1,nly0,nly1,nz0,nz1);
        if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,ntx0,ntx2,nty0,nty2,nlx0,nlx2,nly0,nly2,nz0,nz2);
        if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,ntx1,ntx2,nty1,nty2,nlx1,nlx2,nly1,nly2,nz1,nz2);
        if (ny0<dimy() && ny2>=0) {
          float
            ptxl = (ntx1-ntx0)/(ny1-ny0),
            ptxr = (ntx2-ntx0)/(ny2-ny0),
            ptxn = (ntx2-ntx1)/(ny2-ny1),
            ptyl = (nty1-nty0)/(ny1-ny0),
            ptyr = (nty2-nty0)/(ny2-ny0),
            ptyn = (nty2-nty1)/(ny2-ny1),
            pzl = (nz1-nz0)/(ny1-ny0),
            pzr = (nz2-nz0)/(ny2-ny0),
            pzn = (nz2-nz1)/(ny2-ny1),
            zr  = ny0>=0?nz0:(nz0-ny0*(nz2-nz0)/(ny2-ny0)),
            txr = ny0>=0?ntx0:(ntx0-ny0*(ntx2-ntx0)/(ny2-ny0)),
            tyr = ny0>=0?nty0:(nty0-ny0*(nty2-nty0)/(ny2-ny0)),
            zl  = ny1>=0?(ny0>=0?nz0:(nz0-ny0*(nz1-nz0)/(ny1-ny0))):(pzl=pzn,(nz1-ny1*(nz2-nz1)/(ny2-ny1))),
            txl = ny1>=0?(ny0>=0?ntx0:(ntx0-ny0*(ntx1-ntx0)/(ny1-ny0))):(ptxl=ptxn,(ntx1-ny1*(ntx2-ntx1)/(ny2-ny1))),
            tyl = ny1>=0?(ny0>=0?nty0:(nty0-ny0*(nty1-nty0)/(ny1-ny0))):(ptyl=ptyn,(nty1-ny1*(nty2-nty1)/(ny2-ny1)));
          _cimg_for_triangle3(*this,xleft0,lxleft0,lyleft0,xright0,lxright0,lyright0,y,
                              nx0,ny0,nlx0,nly0,nx1,ny1,nlx1,nly1,nx2,ny2,nlx2,nly2) {
            if (y==ny1) { zl = nz1; txl = ntx1; tyl = nty1; pzl = pzn; ptxl = ptxn; ptyl = ptyn; }
            int
              xleft = xleft0, xright = xright0,
              lxleft = lxleft0, lxright = lxright0,
              lyleft = lyleft0, lyright = lyright0;
            float
              zleft = zl, zright = zr,
              txleft = txl, txright = txr,
              tyleft = tyl, tyright = tyr;
            if (xright<xleft) cimg::swap(xleft,xright,zleft,zright,txleft,txright,tyleft,tyright,lxleft,lxright,lyleft,lyright);
            const int
              dx = xright-xleft,
              dlx = lxright>lxleft?lxright-lxleft:lxleft-lxright,
              dly = lyright>lyleft?lyright-lyleft:lyleft-lyright,
              rlx = dx?(lxright-lxleft)/dx:0,
              rly = dx?(lyright-lyleft)/dx:0,
              slx = lxright>lxleft?1:-1,
              sly = lyright>lyleft?1:-1,
              ndlx = dlx-(dx?dx*(dlx/dx):0),
              ndly = dly-(dx?dx*(dly/dx):0);
            const float
              pentez = (zright-zleft)/dx,
              pentetx = (txright-txleft)/dx,
              pentety = (tyright-tyleft)/dx;
            int errlx = dx>>1, errly = errlx;
            if (xleft<0 && dx) {
              zleft-=xleft*(zright-zleft)/dx;
              lxleft-=xleft*(lxright-lxleft)/dx;
              lyleft-=xleft*(lyright-lyleft)/dx;
              txleft-=xleft*(txright-txleft)/dx;
              tyleft-=xleft*(tyright-tyleft)/dx;
            }
            if (xleft<0) xleft=0;
            if (xright>=dimx()-1) xright=dimx()-1;
            T* ptrd = ptr(xleft,y,0,0);
            if (opacity>=1) for (int x=xleft; x<=xright; ++x) {
              const float invz = 1.0f/zleft;
              const tl l = light(lxleft,lyleft);
              const t *col = texture.ptr((int)(txleft*invz),(int)(tyleft*invz));
              cimg_forV(*this,k) {
                *ptrd = (T)(l<1?l**col:(2-l)**col+(l-1)*maxval);
                ptrd+=whz; col+=twhz;
              }
              ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
              lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0);
              lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0);
            } else for (int x=xleft; x<=xright; ++x) {
              const float invz = 1.0f/zleft;
              const tl l = light(lxleft,lyleft);
              const t *col = texture.ptr((int)(txleft*invz),(int)(tyleft*invz));
              cimg_forV(*this,k) {
                const T val = (T)(l<1?l**col:(2-l)**col+(l-1)*maxval);
                *ptrd = (T)(nopacity*val + *ptrd*copacity);
                ptrd+=whz; col+=twhz;
              }
              ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
              lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0);
              lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0);
            }
            zr+=pzr; txr+=ptxr; tyr+=ptyr; zl+=pzl; txl+=ptxl; tyl+=ptyl;
          }
        }
      }
      return *this;
    }

    // Draw an ellipse on the instance image (inner routine).
    template<typename tc>
      CImg<T>& _draw_ellipse(const int x0, const int y0, const float r1, const float r2, const float ru, const float rv,
                             const tc *const color, const float opacity, const unsigned int pattern) {
      if (!is_empty()) {
        if (!color) throw CImgArgumentException("CImg<%s>::draw_ellipse : Specified color is (null).",pixel_type());
        _draw_scanline(color,opacity);
        const float
          nr1 = cimg::abs(r1), nr2 = cimg::abs(r2),
          norm = (float)std::sqrt(ru*ru+rv*rv),
          u = norm>0?ru/norm:1,
          v = norm>0?rv/norm:0,
          rmax = cimg::max(nr1,nr2),
          l1 = (float)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,
          tymax = y0+yb,
          ymin = tymin<0?0:tymin,
          ymax = tymax>=dimy()?height-1:tymax;
        int oxmin = 0, oxmax = 0;
        bool first_line = true;
        for (int y=ymin; y<=ymax; ++y) {
          const float
            Y = y-y0 + (y<y0?0.5f:-0.5f),
            delta = b*b*Y*Y-a*(c*Y*Y-rmax*rmax),
            sdelta = delta>0?(float)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 an outlined ellipse.
    /**
       \param x0 = X-coordinate of the ellipse center.
       \param y0 = Y-coordinate of the ellipse center.
       \param r1 = First radius of the ellipse.
       \param r2 = Second radius of the ellipse.
       \param ru = X-coordinate of the orientation vector related to the first radius.
       \param rv = Y-coordinate of the orientation vector related to the first radius.
       \param color = array of dimv() values of type \c T, defining the drawing color.
       \param pattern = If zero, the ellipse is filled, else pattern is an integer whose bits describe the outline pattern.
       \param opacity = opacity of the drawing.
    **/
    template<typename tc>
      CImg<T>& draw_ellipse(const int x0, const int y0, const float r1, const float r2, const float ru, const float rv,
                            const tc *const color, const float opacity, const unsigned int pattern) {
      if (pattern) _draw_ellipse(x0,y0,r1,r2,ru,rv,color,opacity,pattern);
      return *this;
    }

    template<typename tc>
      CImg<T>& draw_ellipse(const int x0, const int y0, const float r1, const float r2, const float ru, const float rv,
                            const CImg<tc>& color, const float opacity, const unsigned int pattern) {
      return draw_ellipse(x0,y0,r1,r2,ru,rv,color.data,opacity,pattern);
    }

    //! Draw a filled ellipse.
    /**
       \param x0 = X-coordinate of the ellipse center.
       \param y0 = Y-coordinate of the ellipse center.
       \param r1 = First radius of the ellipse.
       \param r2 = Second radius of the ellipse.
       \param ru = X-coordinate of the orientation vector related to the first radius.
       \param rv = Y-coordinate of the orientation vector related to the first radius.
       \param color = array of dimv() values of type \c T, defining the drawing color.
       \param opacity = opacity of the drawing.
    **/
    template<typename tc>
      CImg<T>& draw_ellipse(const int x0, const int y0, const float r1, const float r2, const float ru, const float rv,
                            const tc *const color, const float opacity=1.0f) {
      return _draw_ellipse(x0,y0,r1,r2,ru,rv,color,opacity,0U);
    }

    template<typename tc>
      CImg<T>& draw_ellipse(const int x0, const int y0, const float r1, const float r2, const float ru, const float rv,
                            const CImg<tc>& color, const float opacity=1.0f) {
      return draw_ellipse(x0,y0,r1,r2,ru,rv,color.data,opacity);
    }

    //! Draw a filled ellipse on the instance image
    /**
       \param x0 = X-coordinate of the ellipse center.
       \param y0 = Y-coordinate of the ellipse center.
       \param tensor = Diffusion tensor describing the ellipse.
       \param color = array of dimv() values of type \c T, defining the drawing color.
       \param opacity = opacity of the drawing.
    **/
    template<typename t, typename tc>
      CImg<T>& draw_ellipse(const int x0, const int y0, const CImg<t> &tensor,
                            const tc *const color, const float opacity=1.0f) {
      CImgList<t> eig = tensor.get_symmetric_eigen();
      const CImg<t> &val = eig[0], &vec = eig[1];
      return draw_ellipse(x0,y0,val(0),val(1),vec(0,0),vec(0,1),color,opacity);
    }

    template<typename t, typename tc>
      CImg<T>& draw_ellipse(const int x0, const int y0, const CImg<t> &tensor,
                            const CImg<tc>& color, const float opacity=1.0f) {
      return draw_ellipse(x0,y0,tensor,color.data,opacity);
    }

    //! Draw an outlined ellipse on the instance image
    /**
       \param x0 = X-coordinate of the ellipse center.
       \param y0 = Y-coordinate of the ellipse center.
       \param tensor = Diffusion tensor describing the ellipse.
       \param color = array of dimv() values of type \c T, defining the drawing color.
       \param pattern = If zero, the ellipse is filled, else pattern is an integer whose bits describe the outline pattern.
       \param opacity = opacity of the drawing.
    **/
    template<typename t, typename tc>
      CImg<T>& draw_ellipse(const int x0, const int y0, const CImg<t> &tensor,
                            const tc *const color, const float opacity, const unsigned int pattern) {
      CImgList<t> eig = tensor.get_symmetric_eigen();
      const CImg<t> &val = eig[0], &vec = eig[1];
      return draw_ellipse(x0,y0,val(0),val(1),vec(0,0),vec(0,1),color,opacity,pattern);
    }

    template<typename t, typename tc>
      CImg<T>& draw_ellipse(const int x0, const int y0, const CImg<t> &tensor,
                            const CImg<tc>& color, const float opacity, const unsigned int pattern) {
      return draw_ellipse(x0,y0,tensor,color.data,opacity,pattern);
    }

    //! Draw a filled circle on the instance image
    /**
       \param x0 X-coordinate of the circle center.
       \param y0 Y-coordinate of the circle center.
       \param radius  Circle radius.
       \param color Array of dimv() values of type \c T, defining the drawing color.
       \param opacity Drawing opacity.
       \note
       - Circle version of the Bresenham's algorithm is used.
    **/
    template<typename tc>
      CImg<T>& draw_circle(const int x0, const int y0, int radius, const tc *const color, const float opacity=1.0f) {
      if (!is_empty()) {
        if (!color) throw CImgArgumentException("CImg<%s>::draw_circle : Specified color is (null).",pixel_type());
        _draw_scanline(color,opacity);
        if (radius<0 || x0-radius>=dimx() || y0+radius<0 || y0-radius>=dimy()) return *this;
        if (y0>=0 && y0<dimy()) _draw_scanline(x0-radius,x0+radius,y0,color,opacity);
        for (int f=1-radius, ddFx=0, ddFy=-(radius<<1), x=0, y=radius; x<y; ) {
          if (f>=0) {
            const int x1 = x0-x, x2 = x0+x, y1 = y0-y, y2 = y0+y;
            if (y1>=0 && y1<dimy()) _draw_scanline(x1,x2,y1,color,opacity);
            if (y2>=0 && y2<dimy()) _draw_scanline(x1,x2,y2,color,opacity);
            f+=(ddFy+=2); --y;
          }
          const bool no_diag = y!=(x++);
          ++(f+=(ddFx+=2));
          const int x1 = x0-y, x2 = x0+y, y1 = y0-x, y2 = y0+x;
          if (no_diag) {
            if (y1>=0 && y1<dimy()) _draw_scanline(x1,x2,y1,color,opacity);
            if (y2>=0 && y2<dimy()) _draw_scanline(x1,x2,y2,color,opacity);
          }
        }
      }
      return *this;
    }

    template<typename tc>
      CImg<T>& draw_circle(const int x0, const int y0, int radius, const CImg<tc>& color, const float opacity=1.0f) {
      return draw_circle(x0,y0,radius,color.data,opacity);
    }

    //! Draw an outlined circle.
    /**
       \param x0 X-coordinate of the circle center.
       \param y0 Y-coordinate of the circle center.
       \param radius Circle radius.
       \param color Array of dimv() values of type \c T, defining the drawing color.
       \param opacity Drawing opacity.
    **/
    template<typename tc>
      CImg<T>& draw_circle(const int x0, const int y0, int radius, const tc *const color, const float opacity, const unsigned int) {
      if (!is_empty()) {
        if (!color) throw CImgArgumentException("CImg<%s>::draw_circle : Specified color is (null).",pixel_type());
        if (radius<0 || x0-radius>=dimx() || y0+radius<0 || y0-radius>=dimy()) return *this;
        if (!radius) return draw_point(x0,y0,color,opacity);
        draw_point(x0-radius,y0,color,opacity).draw_point(x0+radius,y0,color,opacity).
          draw_point(x0,y0-radius,color,opacity).draw_point(x0,y0+radius,color,opacity);
        if (radius==1) return *this;
        for (int f=1-radius, ddFx=0, ddFy=-(radius<<1), x=0, y=radius; x<y; ) {
          if (f>=0) { f+=(ddFy+=2); --y; }
          ++x; ++(f+=(ddFx+=2));
          if (x!=y+1) {
            const int x1 = x0-y, x2 = x0+y, y1 = y0-x, y2 = y0+x, x3 = x0-x, x4 = x0+x, y3 = y0-y, y4 = y0+y;
            draw_point(x1,y1,color,opacity).draw_point(x1,y2,color,opacity).
              draw_point(x2,y1,color,opacity).draw_point(x2,y2,color,opacity);
            if (x!=y)
              draw_point(x3,y3,color,opacity).draw_point(x4,y4,color,opacity).
                draw_point(x4,y3,color,opacity).draw_point(x3,y4,color,opacity);
          }
        }
      }
      return *this;
    }

    template<typename tc>
      CImg<T>& draw_circle(const int x0, const int y0, int radius, const CImg<tc>& color, const float opacity, const unsigned int foo) {
      return draw_circle(x0,y0,radius,color.data,opacity,foo);
    }

    //! Draw a text into the instance image.
    /**
       \param text = a C-string containing the text to display.
       \param x0 = X-coordinate of the text in the instance image.
       \param y0 = Y-coordinate of the text in the instance image.
       \param fgcolor = an array of dimv() values of type \c T, defining the foreground color (0 means 'transparent').
       \param bgcolor = an array of dimv() values of type \c T, defining the background color (0 means 'transparent').
       \param font = List of font characters used for the drawing.
       \param opacity = opacity of the drawing.
       \note Clipping is supported.
       \see get_font().
    **/
    template<typename t>
      CImg<T>& draw_text(const char *const text,
                         const int x0, const int y0,
                         const T *const fgcolor, const T *const bgcolor,
                         const CImgList<t>& font, const float opacity=1.0f) {
      if (!text)
        throw CImgArgumentException("CImg<%s>::draw_text() : Specified input string is (null).",pixel_type());
      if (!font)
        throw CImgArgumentException("CImg<%s>::draw_text() : Specified font (%u,%p) is empty.",
                                    pixel_type(),font.size,font.data);

      if (is_empty()) {
        // If needed, pre-compute needed size of the image
        int x = 0, y = 0, w = 0;
        unsigned char c = 0;
        for (int i=0; i<cimg::strlen(text); ++i) {
          c = text[i];
          switch (c) {
          case '\n': y+=font[' '].height; if (x>w) w = x; x = 0; break;
          case '\t': x+=4*font[' '].width; break;
          default: if (c<font.size) x+=font[c].width;
          }
        }
        if (x!=0 || c=='\n') {
          if (x>w) w=x;
          y+=font[' '].height;
        }
        assign(x0+w,y0+y,1,font[' '].dim,0);
        if (bgcolor) cimg_forV(*this,k) get_shared_channel(k).fill((T)bgcolor[k]);
      }

      int x = x0, y = y0;
      CImg<T> letter;
      for (int i=0; i<cimg::strlen(text); ++i) {
        const unsigned char c = text[i];
        switch (c) {
        case '\n': y+=font[' '].height; x=x0; break;
        case '\t': x+=4*font[' '].width; break;
        default: if (c<font.size) {
          letter = font[c];
          const CImg<T>& mask = (c+256)<(int)font.size?font[c+256]:font[c];
          if (fgcolor) for (unsigned int p=0; p<letter.width*letter.height; ++p)
            if (mask(p)) cimg_forV(*this,k) letter(p,0,0,k) = (T)(letter(p,0,0,k)*fgcolor[k]);
          if (bgcolor) for (unsigned int p=0; p<letter.width*letter.height; ++p)
            if (!mask(p)) cimg_forV(*this,k) letter(p,0,0,k) = (T)bgcolor[k];
          if (!bgcolor && font.size>=512) draw_image(letter,mask,x,y,0,0,(T)1,opacity);
          else draw_image(letter,x,y,0,0,opacity);
          x+=letter.width;
        }
          break;
        }
      }
      return *this;
    }

    template<typename tc, typename t>
      CImg<T>& draw_text(const char *const text,
                         const int x0, const int y0,
                         const CImg<tc>& fgcolor, const CImg<tc>& bgcolor,
                         const CImgList<t>& font, const float opacity=1.0f) {
      const CImg<T> fgcol(fgcolor), bgcol(bgcolor);
      return draw_text(text,x0,y0,fgcol.data,bgcol.data,font,opacity);
    }

    //! Draw a text into the instance image.
    /**
       \param text = a C-string containing the text to display.
       \param x0 = X-coordinate of the text in the instance image.
       \param y0 = Y-coordinate of the text in the instance image.
       \param fgcolor = an array of dimv() values of type \c T, defining the foreground color (0 means 'transparent').
       \param bgcolor = an array of dimv() values of type \c T, defining the background color (0 means 'transparent').
       \param font_size = Height of the desired font (11,13,24,38 or 57)
       \param opacity = opacity of the drawing.
       \note Clipping is supported.
       \see get_font().
    **/
    CImg<T>& draw_text(const char *const text,
                       const int x0, const int y0,
                       const T *const fgcolor, const T *const bgcolor=0,
                       const unsigned int font_size=11, const float opacity=1.0f) {
      return draw_text(text,x0,y0,fgcolor,bgcolor,CImgList<T>::get_font(font_size),opacity);
    }

    template<typename tc>
      CImg<T>& draw_text(const char *const text,
                         const int x0, const int y0,
                         const CImg<tc>& fgcolor, const CImg<tc>& bgcolor,
                         const unsigned int font_size=11, const float opacity=1.0f) {
      const CImg<T> fgcol(fgcolor), bgcol(bgcolor);
      return draw_text(text,x0,y0,fgcol.data,bgcol.data,font_size,opacity);
    }

    //! Draw a text into the instance image.
    /**
       \param x0 X-coordinate of the text in the instance image.
       \param y0 Y-coordinate of the text in the instance image.
       \param fgcolor Array of dimv() values of type \c T, defining the foreground color (0 means 'transparent').
       \param bgcolor Array of dimv() values of type \c T, defining the background color (0 means 'transparent').
       \param font_size Size of the font (nearest match).
       \param opacity Drawing opacity.
       \param format 'printf'-style format string, followed by arguments.
       \note Clipping is supported.
    **/
    CImg<T>& draw_text(const int x0, const int y0,
                       const T *const fgcolor, const T *const bgcolor, const unsigned int font_size,
                       const float opacity, const char *format, ...) {
      char tmp[2048] = { 0 };
      std::va_list ap;
      va_start(ap,format);
      std::vsprintf(tmp,format,ap);
      va_end(ap);
      return draw_text(tmp,x0,y0,fgcolor,bgcolor,font_size,opacity);
    }

    template<typename tc>
      CImg<T>& draw_text(const int x0, const int y0,
                         const CImg<tc>& fgcolor, const CImg<tc>& bgcolor, const unsigned int font_size,
                         const float opacity, const char *format, ...) {
      char tmp[2048] = { 0 };
      std::va_list ap;
      va_start(ap,format);
      std::vsprintf(tmp,format,ap);
      va_end(ap);
      return draw_text(tmp,x0,y0,fgcolor,bgcolor,font_size,opacity);
    }

    //! Draw a text into the instance image.
    /**
       \param x0 X-coordinate of the text in the instance image.
       \param y0 Y-coordinate of the text in the instance image.
       \param fgcolor Array of dimv() values of type \c T, defining the foreground color (0 means 'transparent').
       \param bgcolor Array of dimv() values of type \c T, defining the background color (0 means 'transparent').
       \param font Font used for drawing text.
       \param opacity Drawing opacity.
       \param format 'printf'-style format string, followed by arguments.
       \note Clipping is supported.
    **/
    template<typename t>
      CImg<T>& draw_text(const int x0, const int y0,
                         const T *const fgcolor, const T *const bgcolor,
                         const CImgList<t>& font, const float opacity, const char *format, ...) {
      char tmp[2048] = { 0 };
      std::va_list ap;
      va_start(ap,format);
      std::vsprintf(tmp,format,ap);
      va_end(ap);
      return draw_text(tmp,x0,y0,fgcolor,bgcolor,font,opacity);
    }

    template<typename tc, typename t>
      CImg<T>& draw_text(const int x0, const int y0,
                         const CImg<tc>& fgcolor, const CImg<tc>& bgcolor,
                         const CImgList<t>& font, const float opacity, const char *format, ...) {
      char tmp[2048] = { 0 };
      std::va_list ap;
      va_start(ap,format);
      std::vsprintf(tmp,format,ap);
      va_end(ap);
      return draw_text(tmp,x0,y0,fgcolor,bgcolor,font,opacity);
    }

    //! Draw a vector field in the instance image, using a colormap.
    /**
       \param flow Image of 2d vectors used as input data.
       \param color Image of dimv()-D vectors corresponding to the color of each arrow.
       \param sampling Length (in pixels) between each arrow.
       \param factor Length factor of each arrow (if <0, computed as a percentage of the maximum length).
       \param quiver_type Type of plot. Can be 0 (arrows) or 1 (segments).
       \param opacity Opacity of the drawing.
       \param pattern Used pattern to draw lines.
       \note Clipping is supported.
    **/
    template<typename t1, typename t2>
      CImg<T>& draw_quiver(const CImg<t1>& flow, const t2 *const color,
                           const unsigned int sampling=25, const float factor=-20,
                           const int quiver_type=0, const float opacity=1.0f, const unsigned int pattern=~0U) {
      return draw_quiver(flow,CImg<t2>(color,dim,1,1,1,true),sampling,factor,quiver_type,opacity,pattern);
    }

    //! Draw a vector field in the instance image, using a colormap.
    /**
       \param flow Image of 2d vectors used as input data.
       \param color Image of dimv()-D vectors corresponding to the color of each arrow.
       \param sampling Length (in pixels) between each arrow.
       \param factor Length factor of each arrow (if <0, computed as a percentage of the maximum length).
       \param quiver_type Type of plot. Can be 0 (arrows) or 1 (segments).
       \param opacity Opacity of the drawing.
       \param pattern Used pattern to draw lines.
       \note Clipping is supported.
    **/
    template<typename t1, typename t2>
      CImg<T>& draw_quiver(const CImg<t1>& flow, const CImg<t2>& color,
                           const unsigned int sampling=25, const float factor=-20,
                           const int quiver_type=0, const float opacity=1.0f, const unsigned int pattern=~0U) {
      if (!is_empty()) {
        if (!flow || flow.dim!=2)
          throw CImgArgumentException("CImg<%s>::draw_quiver() : Specified flow (%u,%u,%u,%u,%p) has wrong dimensions.",
                                      pixel_type(),flow.width,flow.height,flow.depth,flow.dim,flow.data);
        if (sampling<=0)
          throw CImgArgumentException("CImg<%s>::draw_quiver() : Incorrect sampling value = %g",pixel_type(),sampling);
        const bool colorfield = (color.width==flow.width && color.height==flow.height && color.depth==1 && color.dim==dim);
        if (is_overlapping(flow)) return draw_quiver(+flow,color,sampling,factor,quiver_type,opacity,pattern);

        float vmax,fact;
        if (factor<=0) {
          float m, M = (float)flow.get_norm_pointwise(2).maxmin(m);
          vmax = (float)cimg::max(cimg::abs(m),cimg::abs(M));
          fact = -factor;
        } else { fact = factor; vmax = 1; }

        for (unsigned int y=sampling/2; y<height; y+=sampling)
          for (unsigned int x=sampling/2; x<width; x+=sampling) {
            const unsigned int X = x*flow.width/width, Y = y*flow.height/height;
            float u = (float)flow(X,Y,0,0)*fact/vmax, v = (float)flow(X,Y,0,1)*fact/vmax;
            if (!quiver_type) {
              const int xx = x+(int)u, yy = y+(int)v;
              if (colorfield) draw_arrow(x,y,xx,yy,color.get_vector_at(X,Y).data,45.0f,sampling/5.0f,opacity,pattern);
              else draw_arrow(x,y,xx,yy,color,45.0f,sampling/5.0f,opacity,pattern);
            } else {
              if (colorfield) draw_line((int)(x-0.5*u),(int)(y-0.5*v),(int)(x+0.5*u),(int)(y+0.5*v),color.get_vector_at(X,Y),opacity,pattern);
              else draw_line((int)(x-0.5*u),(int)(y-0.5*v),(int)(x+0.5*u),(int)(y+0.5*v),color,opacity,pattern);
            }
          }
      }
      return *this;
    }

    //! Draw a 1D graph on the instance image.
    /**
       \param data Image containing the graph values I = f(x).
       \param color Array of dimv() values of type \c T, defining the drawing color.
       \param gtype Define the type of the plot :
                      - 0 = Plot using points clouds.
                      - 1 = Plot using linear interpolation (segments).
                      - 2 = Plot with bars.
                      - 3 = Plot using cubic interpolation (3-polynomials).
       \param ymin Lower bound of the y-range.
       \param ymax Upper bound of the y-range.
       \param opacity Drawing opacity.
       \param pattern Drawing pattern.
       \note
         - if \c ymin==ymax==0, the y-range is computed automatically from the input sample.
       \see draw_axis().
    **/
    template<typename t, typename tc>
    CImg<T>& draw_graph(const CImg<t>& data, const tc *const color, const unsigned int gtype=1,
                        const double ymin=0, const double ymax=0, const float opacity=1.0f,
                        const unsigned int pattern=~0U) {
      if (!is_empty()) {
        if (!color) throw CImgArgumentException("CImg<%s>::draw_graph() : Specified color is (null)",pixel_type());
        tc *color1 = new tc[dim], *color2 = new tc[dim];
        cimg_forV(*this,k) { color1[k]=(tc)(color[k]*0.6f); color2[k]=(tc)(color[k]*0.3f); }
        float m = (float)ymin, M = (float)ymax;
        if (ymin==ymax) m = (float)data.maxmin(M);
        if (m==M) { --m; ++M; }
        const float ca = height>1?(float)(M-m)/(height-1):0;
        const int Y0 = (int)(-m/ca);
        int pY = 0;
	bool init_hatch = true;
        if (gtype<3) cimg_foroff(data,off) {
          const int Y = (int)((data[off]-m)/ca);
          switch (gtype) {
          case 0: { // plot with points
            const unsigned int X = off*width/data.size();
            draw_point(X,Y,color,opacity);
          } break;
          case 1: // plot with segments
            if (off>0) {
	      draw_line((int)((off-1)*width/data.size()),pY,(int)(off*width/data.size()),Y,color,opacity,pattern,init_hatch);
	      init_hatch = false;
	    }
            break;
          case 2: { // plot with bars
            const unsigned int X = off*width/data.size(), nX = (off+1)*width/data.size()-1;
            draw_rectangle(X,(int)Y0,nX,Y,color1,opacity);
            draw_line(X,Y,X,(int)Y0,color2,opacity);
            draw_line(X,(int)Y0,nX,(int)Y0,Y<=Y0?color2:color,opacity);
            draw_line(nX,Y,nX,(int)Y0,color,opacity);
            draw_line(X,Y,nX,Y,Y<=Y0?color:color2,opacity);
          } break;
          }
          pY = Y;
        } else { // plot with cubic interpolation
          const CImg<t> ndata = data.get_shared_points(0,data.size()-1);
          cimg_forX(*this,x) {
            const int Y = (int)((ndata.cubic_pix1d((float)x*ndata.width/width)-m)/ca);
            if (x>0) draw_line(x,pY,x+1,Y,color,opacity,pattern,init_hatch);
	    init_hatch = false;
            pY = Y;
          }
        }
        delete[] color1; delete[] color2;
      }
      return *this;
    }

    template<typename t, typename tc>
    CImg<T>& draw_graph(const CImg<t>& data, const CImg<tc>& color, const unsigned int gtype=1,
                        const double ymin=0, const double ymax=0, const float opacity=1.0f,
                        const unsigned int pattern=~0U) {
      return draw_graph(data,color.data,gtype,ymin,ymax,opacity,pattern);
    }

    //! Draw a labeled horizontal axis on the instance image.
    /**
       \param xvalues Lower bound of the x-range.
       \param y Y-coordinate of the horizontal axis in the instance image.
       \param color Array of dimv() values of type \c T, defining the drawing color.
       \param opacity Drawing opacity.
       \param pattern Drawing pattern.
       \note if \c precision==0, precision of the labels is automatically computed.
       \see draw_graph().
    **/
    template<typename t, typename tc>
      CImg<T>& draw_axis(const CImg<t>& xvalues, const int y,
                         const tc *const color, const float opacity=1.0f, const unsigned int pattern=~0U) {
      if (!is_empty()) {
        int siz = (int)xvalues.size()-1;
        if (siz<=0) draw_line(0,y,width-1,y,color,opacity,pattern);
        else {
          if (xvalues[0]<xvalues[siz]) draw_arrow(0,y,width-1,y,color,30,5,opacity,pattern);
          else draw_arrow(width-1,y,0,y,color,30,5,opacity,pattern);
          const int yt = (y+14)<dimy()?(y+3):(y-14);
          char txt[32];
          cimg_foroff(xvalues,x) {
            std::sprintf(txt,"%g",(double)xvalues(x));
            const int xi=(int)(x*(width-1)/siz), xt = xi-(int)std::strlen(txt)*3;
            draw_point(xi,y-1,color,opacity).draw_point(xi,y+1,color,opacity).
              draw_text(txt,xt<0?0:xt,yt,color,(tc*)0,11,opacity);
          }
        }
      }
      return *this;
    }

    template<typename t, typename tc>
      CImg<T>& draw_axis(const CImg<t>& xvalues, const int y,
                         const CImg<tc>& color, const float opacity=1.0f, const unsigned int pattern=~0U) {
      return draw_axis(xvalues,y,color.data,opacity,pattern);
    }

    //! Draw a labeled vertical axis on the instance image.
    template<typename t, typename tc>
      CImg<T>& draw_axis(const int x, const CImg<t>& yvalues,
                         const tc *const color, const float opacity=1.0f, const unsigned int pattern=~0U) {
      if (!is_empty()) {
        int siz = (int)yvalues.size()-1;
        if (siz<=0) draw_line(x,0,x,height-1,color,opacity,pattern);
        else {
          if (yvalues[0]<yvalues[siz]) draw_arrow(x,0,x,height-1,color,30,5,opacity,pattern);
          else draw_arrow(x,height-1,x,0,color,30,5,opacity,pattern);
          char txt[32];
          cimg_foroff(yvalues,y) {
            std::sprintf(txt,"%g",(double)yvalues(y));
            const int
              yi = (int)(y*(height-1)/siz),
              tmp = yi-5,
              nyi = tmp<0?0:(tmp>=dimy()-11?dimy()-11:tmp),
              xt = x-(int)std::strlen(txt)*7;
            draw_point(x-1,yi,color,opacity).draw_point(x+1,yi,color,opacity);
            if (xt>0) draw_text(txt,xt,nyi,color,(tc*)0,11,opacity);
            else draw_text(txt,x+3,nyi,color,(tc*)0,11,opacity);
          }
        }
      }
      return *this;
    }

    template<typename t, typename tc>
      CImg<T>& draw_axis(const int x, const CImg<t>& yvalues,
                         const CImg<tc>& color, const float opacity=1.0f, const unsigned int pattern=~0U) {
      return draw_axis(x,yvalues,color.data,opacity,pattern);
    }

    //! Draw a labeled horizontal+vertical axis on the instance image.
    template<typename tx, typename ty, typename tc>
      CImg<T>& draw_axis(const CImg<tx>& xvalues, const CImg<ty>& yvalues,
                         const tc *const color,
                         const float opacity=1.0f,
                         const unsigned int patternx=~0U, const unsigned int patterny=~0U) {
      if (!is_empty()) {
        const CImg<tx> nxvalues(xvalues.data,xvalues.size(),1,1,1,true);
        const int sizx = (int)xvalues.size()-1, wm1 = (int)(width)-1;
        if (sizx>0) {
          float ox = (float)nxvalues[0];
          for (unsigned int x=1; x<width; ++x) {
            const float nx = (float)nxvalues.linear_pix1d((float)x*sizx/wm1);
            if (nx*ox<=0) { draw_axis(nx==0?x:x-1,yvalues,color,opacity,patterny); break; }
            ox = nx;
          }
        }
        const CImg<ty> nyvalues(yvalues.data,yvalues.size(),1,1,1,true);
        const int sizy = (int)yvalues.size()-1, hm1 = (int)(height)-1;
        if (sizy>0) {
          float oy = (float)nyvalues[0];
          for (unsigned int y=1; y<height; ++y) {
            const float ny = (float)nyvalues.linear_pix1d((float)y*sizy/hm1);
            if (ny*oy<=0) { draw_axis(xvalues,ny==0?y:y-1,color,opacity,patternx); break; }
            oy = ny;
          }
        }
      }
      return *this;
    }

    template<typename tx, typename ty, typename tc>
      CImg<T>& draw_axis(const CImg<tx>& xvalues, const CImg<ty>& yvalues,
                         const CImg<tc>& color,
                         const float opacity=1.0f,
                         const unsigned int patternx=~0U, const unsigned int patterny=~0U) {
      return draw_axis(xvalues,yvalues,color.data,opacity,patternx,patterny);
    }

    //! Draw a labeled horizontal+vertical axis on the instance image.
    template<typename tc>
      CImg<T>& draw_axis(const float x0, const float x1, const float y0, const float y1,
                         const tc *const color,
                         const int subdivisionx=-60, const int subdivisiony=-60,
                         const float precisionx=0, const float precisiony=0,
                         const float opacity=1.0f,
                         const unsigned int patternx=~0U, const unsigned int patterny=~0U) {
      const float dx = cimg::abs(x1-x0), dy = cimg::abs(y1-y0);
      const float
        px = (precisionx==0)?(float)std::pow(10.0,(int)std::log10(dx)-2.0):precisionx,
        py = (precisiony==0)?(float)std::pow(10.0,(int)std::log10(dy)-2.0):precisiony;
      return draw_axis(CImg<float>::sequence(subdivisionx>0?subdivisionx:1-dimx()/subdivisionx,x0,x1).round(px),
                       CImg<float>::sequence(subdivisiony>0?subdivisiony:1-dimy()/subdivisiony,y0,y1).round(py),
                       color,opacity,patternx,patterny);
    }

    template<typename tc>
      CImg<T>& draw_axis(const float x0, const float x1, const float y0, const float y1,
                         const CImg<tc>& color,
                         const int subdivisionx=-60, const int subdivisiony=-60,
                         const float precisionx=0, const float precisiony=0,
                         const float opacity=1.0f,
                         const unsigned int patternx=~0U, const unsigned int patterny=~0U) {
      return draw_axis(x0,x1,y0,y1,color.data,subdivisionx,subdivisiony,precisionx,precisiony,opacity,patternx,patterny);
    }

    //! Draw grid on the instance image
    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.0f, const unsigned int patternx=~0U, const unsigned int patterny=~0U) {
      if (!is_empty()) {
        if (xvalues) cimg_foroff(xvalues,x) {
          const int xi = (int)xvalues[x];
          if (xi>=0 && xi<dimx()) draw_line(xi,0,xi,height-1,color,opacity,patternx);
        }
        if (yvalues) cimg_foroff(yvalues,y) {
          const int yi = (int)yvalues[y];
          if (yi>=0 && yi<dimy()) draw_line(0,yi,width-1,yi,color,opacity,patterny);
        }
      }
      return *this;
    }

    template<typename tx, typename ty, typename tc>
      CImg<T>& draw_grid(const CImg<tx>& xvalues, const CImg<ty>& yvalues, const CImg<tc>& color,
                         const float opacity=1.0f, const unsigned int patternx=~0U, const unsigned int patterny=~0U) {
      return draw_grid(xvalues,yvalues,color.data,opacity,patternx,patterny);
    }

    //! Draw grid on the instance image
    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.0f, const unsigned int patternx=~0U, const unsigned int patterny=~0U) {
      CImg<unsigned int> seqx, seqy;
      if (deltax!=0) {
        const float dx = deltax>0?deltax:width*-deltax/100;
        const unsigned int nx = (unsigned int)(width/dx);
        seqx = CImg<unsigned int>::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<unsigned int>::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);
    }

    template<typename tc>
      CImg<T>& draw_grid(const float deltax,  const float deltay,
                         const float offsetx, const float offsety,
                         const bool invertx, const bool inverty,
                         const CImg<tc>& color,
                         const float opacity=1.0f, const unsigned int patternx=~0U, const unsigned int patterny=~0U) {
      return draw_grid(deltax,deltay,offsetx,offsety,invertx,inverty,color.data,opacity,patternx,patterny);
    }

    //! Draw a 3D filled region starting from a point (\c x,\c y,\ z) in the instance image.
    /**
       \param x X-coordinate of the starting point of the region to fill.
       \param y Y-coordinate of the starting point of the region to fill.
       \param z Z-coordinate of the starting point of the region to fill.
       \param color An array of dimv() values of type \c T, defining the drawing color.
       \param region Image that will contain the mask of the filled region mask, as an output.
       \param sigma Tolerance concerning neighborhood values.
       \param opacity Opacity of the drawing.
       \param high_connexity Tells if 8-connexity must be used (only for 2D images).
       \return \p region is initialized with the binary mask of the filled region.
    **/
    template<typename tc, typename t>
      CImg<T>& draw_fill(const int x, const int y, const int z,
                         const tc *const color, CImg<t>& region, const float sigma=0,
                         const float opacity=1.0f, const bool high_connexity=false) {

#define _cimg_draw_fill_test(x,y,z,res) if (region(x,y,z)) res = false; else { \
  res = true; \
  const T *reference_col = reference_color.ptr() + dim, *ptrs = ptr(x,y,z) + siz; \
  for (unsigned int i = dim; res && i; --i) { ptrs-=whz; res = (cimg::abs(*ptrs - *(--reference_col))<=sigma); } \
  region(x,y,z) = (t)(res?1:noregion); \
}

#define _cimg_draw_fill_set(x,y,z) { \
  const tc *col = color; \
  T *ptrd = ptr(x,y,z); \
  if (opacity>=1) cimg_forV(*this,k) { *ptrd = (T)*(col++); ptrd+=whz; } \
  else cimg_forV(*this,k) { *ptrd = (T)(*(col++)*nopacity + *ptrd*copacity); ptrd+=whz; } \
}

#define _cimg_draw_fill_insert(x,y,z) { \
  if (posr1>=remaining.height) remaining.resize(3,remaining.height<<1,1,1,0); \
  unsigned int *ptrr = remaining.ptr(0,posr1); \
  *(ptrr++) = x; *(ptrr++) = y; *(ptrr++) = z; ++posr1; \
}

#define _cimg_draw_fill_test_neighbor(x,y,z,cond) if (cond) { \
  const unsigned int tx = x, ty = y, tz = z; \
  _cimg_draw_fill_test(tx,ty,tz,res); if (res) _cimg_draw_fill_insert(tx,ty,tz); \
}

      if (!color) throw CImgArgumentException("CImg<%s>::draw_fill() : Specified color is (null).",pixel_type());
      region.assign(width,height,depth,1,(t)0);
      if (x>=0 || x<dimx() || y>=0 || y<dimy() || z>=0 || z<dimz()) {
        typedef typename cimg::last<T,unsigned int>::type itype;
        const float nopacity = cimg::abs(opacity), copacity = 1-cimg::max(opacity,0.0f);
        const unsigned int whz = width*height*depth, siz = dim*whz, W1 = width-1, H1 = height-1, D1 = depth-1;
        const bool threed = depth>1;
        const CImg<T> reference_color = get_vector_at(x,y,z);
        CImg<itype> remaining(3,512,1,1,0);
        remaining(0,0) = x; remaining(1,0) = y; remaining(2,0) = z;
        unsigned int posr0 = 0, posr1 = 1;
        region(x,y,z) = (t)1;
        const t noregion = ((t)1==(t)2)?(t)0:(t)(-1);
        if (threed) do { // 3D version of the filling algorithm
          const unsigned int *pcurr = remaining.ptr(0,posr0++), xc = *(pcurr++), yc = *(pcurr++), zc = *(pcurr++);
          if (posr0>=512) { remaining.translate(0,posr0); posr1-=posr0; posr0 = 0; }
          bool cont, res;
          unsigned int nxc = xc;
          do { // X-backward
            _cimg_draw_fill_set(nxc,yc,zc);
            _cimg_draw_fill_test_neighbor(nxc,yc-1,zc,yc!=0);
            _cimg_draw_fill_test_neighbor(nxc,yc+1,zc,yc<H1);
            _cimg_draw_fill_test_neighbor(nxc,yc,zc-1,zc!=0);
            _cimg_draw_fill_test_neighbor(nxc,yc,zc+1,zc<D1);
            if (nxc) { --nxc; _cimg_draw_fill_test(nxc,yc,zc,cont); } else cont = false;
          } while (cont);
          nxc = xc;
          do { // X-forward
            if ((++nxc)<=W1) { _cimg_draw_fill_test(nxc,yc,zc,cont); } else cont = false;
            if (cont) {
              _cimg_draw_fill_set(nxc,yc,zc);
              _cimg_draw_fill_test_neighbor(nxc,yc-1,zc,yc!=0);
              _cimg_draw_fill_test_neighbor(nxc,yc+1,zc,yc<H1);
              _cimg_draw_fill_test_neighbor(nxc,yc,zc-1,zc!=0);
              _cimg_draw_fill_test_neighbor(nxc,yc,zc+1,zc<D1);
            }
          } while (cont);
          unsigned int nyc = yc;
          do { // Y-backward
            if (nyc) { --nyc; _cimg_draw_fill_test(xc,nyc,zc,cont); } else cont = false;
            if (cont) {
              _cimg_draw_fill_set(xc,nyc,zc);
              _cimg_draw_fill_test_neighbor(xc-1,nyc,zc,xc!=0);
              _cimg_draw_fill_test_neighbor(xc+1,nyc,zc,xc<W1);
              _cimg_draw_fill_test_neighbor(xc,nyc,zc-1,zc!=0);
              _cimg_draw_fill_test_neighbor(xc,nyc,zc+1,zc<D1);
            }
          } while (cont);
          nyc = yc;
          do { // Y-forward
            if ((++nyc)<=H1) { _cimg_draw_fill_test(xc,nyc,zc,cont); } else cont = false;
            if (cont) {
              _cimg_draw_fill_set(xc,nyc,zc);
              _cimg_draw_fill_test_neighbor(xc-1,nyc,zc,xc!=0);
              _cimg_draw_fill_test_neighbor(xc+1,nyc,zc,xc<W1);
              _cimg_draw_fill_test_neighbor(xc,nyc,zc-1,zc!=0);
              _cimg_draw_fill_test_neighbor(xc,nyc,zc+1,zc<D1);
            }
          } while (cont);
          unsigned int nzc = zc;
          do { // Z-backward
            if (nzc) { --nzc; _cimg_draw_fill_test(xc,yc,nzc,cont); } else cont = false;
            if (cont) {
              _cimg_draw_fill_set(xc,yc,nzc);
              _cimg_draw_fill_test_neighbor(xc-1,yc,nzc,xc!=0);
              _cimg_draw_fill_test_neighbor(xc+1,yc,nzc,xc<W1);
              _cimg_draw_fill_test_neighbor(xc,yc-1,nzc,yc!=0);
              _cimg_draw_fill_test_neighbor(xc,yc+1,nzc,yc<H1);
            }
          } while (cont);
          nzc = zc;
          do { // Z-forward
            if ((++nzc)<=D1) { _cimg_draw_fill_test(xc,yc,nzc,cont); } else cont = false;
            if (cont) {
              _cimg_draw_fill_set(xc,nyc,zc);
              _cimg_draw_fill_test_neighbor(xc-1,yc,nzc,xc!=0);
              _cimg_draw_fill_test_neighbor(xc+1,yc,nzc,xc<W1);
              _cimg_draw_fill_test_neighbor(xc,yc-1,nzc,yc!=0);
              _cimg_draw_fill_test_neighbor(xc,yc+1,nzc,yc<H1);
            }
          } while (cont);
        } while (posr1>posr0);
        else do { // 2D version of the filling algorithm
          const unsigned int *pcurr = remaining.ptr(0,posr0++), xc = *(pcurr++), yc = *(pcurr++);
          if (posr0>=512) { remaining.translate(0,posr0); posr1-=posr0; posr0 = 0; }
          bool cont, res;
          unsigned int nxc = xc;
          do { // X-backward
            _cimg_draw_fill_set(nxc,yc,0);
            _cimg_draw_fill_test_neighbor(nxc,yc-1,0,yc!=0);
            _cimg_draw_fill_test_neighbor(nxc,yc+1,0,yc<H1);
            if (high_connexity) {
              _cimg_draw_fill_test_neighbor(nxc-1,yc-1,0,(nxc!=0 && yc!=0));
              _cimg_draw_fill_test_neighbor(nxc+1,yc-1,0,(nxc<W1 && yc!=0));
              _cimg_draw_fill_test_neighbor(nxc-1,yc+1,0,(nxc!=0 && yc<H1));
              _cimg_draw_fill_test_neighbor(nxc+1,yc+1,0,(nxc<W1 && yc<H1));
            }
            if (nxc) { --nxc; _cimg_draw_fill_test(nxc,yc,0,cont); } else cont = false;
          } while (cont);
          nxc = xc;
          do { // X-forward
            if ((++nxc)<=W1) { _cimg_draw_fill_test(nxc,yc,0,cont); } else cont = false;
            if (cont) {
              _cimg_draw_fill_set(nxc,yc,0);
              _cimg_draw_fill_test_neighbor(nxc,yc-1,0,yc!=0);
              _cimg_draw_fill_test_neighbor(nxc,yc+1,0,yc<H1);
              if (high_connexity) {
                _cimg_draw_fill_test_neighbor(nxc-1,yc-1,0,(nxc!=0 && yc!=0));
                _cimg_draw_fill_test_neighbor(nxc+1,yc-1,0,(nxc<W1 && yc!=0));
                _cimg_draw_fill_test_neighbor(nxc-1,yc+1,0,(nxc!=0 && yc<H1));
                _cimg_draw_fill_test_neighbor(nxc+1,yc+1,0,(nxc<W1 && yc<H1));
              }
            }
          } while (cont);
          unsigned int nyc = yc;
          do { // Y-backward
            if (nyc) { --nyc; _cimg_draw_fill_test(xc,nyc,0,cont); } else cont = false;
            if (cont) {
              _cimg_draw_fill_set(xc,nyc,0);
              _cimg_draw_fill_test_neighbor(xc-1,nyc,0,xc!=0);
              _cimg_draw_fill_test_neighbor(xc+1,nyc,0,xc<W1);
              if (high_connexity) {
                _cimg_draw_fill_test_neighbor(xc-1,nyc-1,0,(xc!=0 && nyc!=0));
                _cimg_draw_fill_test_neighbor(xc+1,nyc-1,0,(xc<W1 && nyc!=0));
                _cimg_draw_fill_test_neighbor(xc-1,nyc+1,0,(xc!=0 && nyc<H1));
                _cimg_draw_fill_test_neighbor(xc+1,nyc+1,0,(xc<W1 && nyc<H1));
              }
            }
          } while (cont);
          nyc = yc;
          do { // Y-forward
            if ((++nyc)<=H1) { _cimg_draw_fill_test(xc,nyc,0,cont); } else cont = false;
            if (cont) {
              _cimg_draw_fill_set(xc,nyc,0);
              _cimg_draw_fill_test_neighbor(xc-1,nyc,0,xc!=0);
              _cimg_draw_fill_test_neighbor(xc+1,nyc,0,xc<W1);
              if (high_connexity) {
                _cimg_draw_fill_test_neighbor(xc-1,nyc-1,0,(xc!=0 && nyc!=0));
                _cimg_draw_fill_test_neighbor(xc+1,nyc-1,0,(xc<W1 && nyc!=0));
                _cimg_draw_fill_test_neighbor(xc-1,nyc+1,0,(xc!=0 && nyc<H1));
                _cimg_draw_fill_test_neighbor(xc+1,nyc+1,0,(xc<W1 && nyc<H1));
              }
            }
          } while (cont);
        } while (posr1>posr0);
        if (noregion) cimg_for(region,ptr,t) if (*ptr==noregion) *ptr = (t)0;
      }
      return *this;
    }

    template<typename tc, typename t>
      CImg<T>& draw_fill(const int x, const int y, const int z,
                         const CImg<tc>& color, CImg<t>& region, const float sigma=0,
                         const float opacity=1.0f, const bool high_connexity=false) {
      return draw_fill(x,y,z,color.data,region,sigma,opacity,high_connexity);
    }

    //! Draw a 3D filled region starting from a point (\c x,\c y,\ z) in the instance image.
    /**
       \param x = X-coordinate of the starting point of the region to fill.
       \param y = Y-coordinate of the starting point of the region to fill.
       \param z = Z-coordinate of the starting point of the region to fill.
       \param color = an array of dimv() values of type \c T, defining the drawing color.
       \param sigma = tolerance concerning neighborhood values.
       \param opacity = opacity of the drawing.
    **/
    template<typename tc>
      CImg<T>& draw_fill(const int x, const int y, const int z, const tc *const color, const float sigma=0,
                         const float opacity=1.0f, const bool high_connexity=false) {
      CImg<bool> tmp;
      return draw_fill(x,y,z,color,tmp,sigma,opacity,high_connexity);
    }

    template<typename tc>
      CImg<T>& draw_fill(const int x, const int y, const int z, const CImg<tc>& color, const float sigma=0,
                         const float opacity=1.0f, const bool high_connexity=false) {
      return draw_fill(x,y,z,color.data,sigma,opacity,high_connexity);
    }

    //! Draw a 2D filled region starting from a point (\c x,\c y) in the instance image.
    /**
       \param x = X-coordinate of the starting point of the region to fill.
       \param y = Y-coordinate of the starting point of the region to fill.
       \param color = an array of dimv() values of type \c T, defining the drawing color.
       \param sigma = tolerance concerning neighborhood values.
       \param opacity = opacity of the drawing.
    **/
    template<typename tc>
      CImg<T>& draw_fill(const int x, const int y, const tc *const color, const float sigma=0,
                         const float opacity=1.0f, const bool high_connexity=false) {
      CImg<bool> tmp;
      return draw_fill(x,y,0,color,tmp,sigma,opacity,high_connexity);
    }

    template<typename tc>
      CImg<T>& draw_fill(const int x, const int y, const CImg<tc>& color, const float sigma=0,
                         const float opacity=1.0f, const bool high_connexity=false) {
      return draw_fill(x,y,color.data,sigma,opacity,high_connexity);
    }

    //! Draw a plasma square in the instance image.
    /**
       \param x0 = X-coordinate of the upper-left corner of the plasma.
       \param y0 = Y-coordinate of the upper-left corner of the plasma.
       \param x1 = X-coordinate of the lower-right corner of the plasma.
       \param y1 = Y-coordinate of the lower-right corner of the plasma.
       \param alpha = Alpha-parameter of the plasma.
       \param beta = Beta-parameter of the plasma.
       \param opacity = opacity of the drawing.
    **/
    CImg<T>& draw_plasma(const int x0, const int y0, const int x1, const int y1,
                         const double alpha=1.0, const double beta=1.0, const float opacity=1.0f) {
      if (!is_empty()) {
        typedef typename cimg::superset<T,float>::type ftype;
        const float nopacity = cimg::abs(opacity), copacity = 1-cimg::max(opacity,0.0f);
        int nx0 = x0, nx1 = x1, ny0 = y0, ny1 = y1;
        if (nx1<nx0) cimg::swap(nx0,nx1);
        if (ny1<ny0) cimg::swap(ny0,ny1);
        if (nx0<0) nx0 = 0;
        if (nx1>=dimx()) nx1 = width-1;
        if (ny0<0) ny0 = 0;
        if (ny1>=dimy()) ny1 = height-1;
        const int xc = (nx0+nx1)/2, yc = (ny0+ny1)/2, dx = (xc-nx0), dy = (yc-ny0);
        const ftype dc = (ftype)(std::sqrt((double)(dx*dx+dy*dy))*alpha + beta);
        ftype val = 0;
        cimg_forV(*this,k) {
          if (opacity>=1) {
            const ftype
              val0 = (*this)(nx0,ny0,0,k), val1 = (*this)(nx1,ny0,0,k),
              val2 = (*this)(nx0,ny1,0,k), val3 = (*this)(nx1,ny1,0,k);
            (*this)(xc,ny0,0,k) = (T)((val0+val1)/2);
            (*this)(xc,ny1,0,k) = (T)((val2+val3)/2);
            (*this)(nx0,yc,0,k) = (T)((val0+val2)/2);
            (*this)(nx1,yc,0,k) = (T)((val1+val3)/2);
            do {
              val = (ftype)(0.25f*((ftype)((*this)(nx0,ny0,0,k)) +
                                   (ftype)((*this)(nx1,ny0,0,k)) +
                                   (ftype)((*this)(nx1,ny1,0,k)) +
                                   (ftype)((*this)(nx0,ny1,0,k))) +
                            dc*cimg::grand());
            } while (val<(ftype)cimg::type<T>::min() || val>(ftype)cimg::type<T>::max());
            (*this)(xc,yc,0,k)  = (T)val;
          } else {
            const ftype
              val0 = (*this)(nx0,ny0,0,k), val1 = (*this)(nx1,ny0,0,k),
              val2 = (*this)(nx0,ny1,0,k), val3 = (*this)(nx1,ny1,0,k);
            (*this)(xc,ny0,0,k) = (T)(((val0+val1)*nopacity + copacity*(*this)(xc,ny0,0,k))/2);
            (*this)(xc,ny1,0,k) = (T)(((val2+val3)*nopacity + copacity*(*this)(xc,ny1,0,k))/2);
            (*this)(nx0,yc,0,k) = (T)(((val0+val2)*nopacity + copacity*(*this)(nx0,yc,0,k))/2);
            (*this)(nx1,yc,0,k) = (T)(((val1+val3)*nopacity + copacity*(*this)(nx1,yc,0,k))/2);
            do {
              val = (ftype)(0.25f*(((ftype)((*this)(nx0,ny0,0,k)) +
                                    (ftype)((*this)(nx1,ny0,0,k)) +
                                    (ftype)((*this)(nx1,ny1,0,k)) +
                                    (ftype)((*this)(nx0,ny1,0,k))) +
                                   dc*cimg::grand())*nopacity + copacity*(*this)(xc,yc,0,k));
            } while (val<(ftype)cimg::type<T>::min() || val>(ftype)cimg::type<T>::max());
            (*this)(xc,yc,0,k)  = (T)val;
          }
        }
        if (xc!=nx0 || yc!=ny0) {
          draw_plasma(nx0,ny0,xc,yc,alpha,beta,opacity);
          draw_plasma(xc,ny0,nx1,yc,alpha,beta,opacity);
          draw_plasma(nx0,yc,xc,ny1,alpha,beta,opacity);
          draw_plasma(xc,yc,nx1,ny1,alpha,beta,opacity);
        }
      }
      return *this;
    }

    //! Draw a plasma in the instance image.
    /**
       \param alpha = Alpha-parameter of the plasma.
       \param beta = Beta-parameter of the plasma.
       \param opacity = opacity of the drawing.
    **/
    CImg<T>& draw_plasma(const double alpha=1.0, const double beta=1.0, const float opacity=1.0f) {
      return draw_plasma(0,0,width-1,height-1,alpha,beta,opacity);
    }

    //! Draw a quadratic Mandelbrot or Julia fractal set, computed using the Escape Time Algorithm.
    template<typename tc>
    CImg<T>& draw_mandelbrot(const int x0, const int y0, const int x1, const int y1,
                             const CImg<tc>& color_palette,
                             const double z0r=-2, const double z0i=-2, const double z1r=2, const double z1i=2,
                             const unsigned int itermax=255,
                             const bool normalized_iteration=false,
                             const bool julia_set=false,
                             const double paramr=0, const double parami=0,
                             const float opacity=1.0f) {
      if (is_empty()) return *this;
      CImg<tc> palette;
      if (color_palette) palette.assign(color_palette.data,color_palette.size()/color_palette.dim,1,1,color_palette.dim,true);
      if (palette && palette.dim!=dim)
        throw CImgArgumentException("CImg<%s>::draw_mandelbrot() : Specified color palette (%u,%u,%u,%u,%p) is not \n"
                                    "compatible with instance image (%u,%u,%u,%u,%p).",
                                    pixel_type(),color_palette.width,color_palette.height,color_palette.depth,color_palette.dim,
                                    color_palette.data,width,height,depth,dim,data);
      const float nopacity = cimg::abs(opacity), copacity = 1-cimg::max(opacity,0.0f), ln2 = (float)std::log(2.0);
      unsigned int iter = 0;
      cimg_for_inXY(*this,x0,y0,x1,y1,p,q) {
        const double x = z0r + p*(z1r-z0r)/width, y = z0i + q*(z1i-z0i)/height;
        double zr, zi, cr, ci;
        if (julia_set) { zr = x; zi = y; cr = paramr; ci = parami; }
        else { zr = paramr; zi = parami; cr = x; ci = y; }
        for (iter=1; zr*zr + zi*zi<=4 && iter<=itermax; ++iter) {
          const double temp = zr*zr - zi*zi + cr;
          zi = 2*zr*zi + ci;
          zr = temp;
        }
        if (iter>itermax) {
          if (palette) {
            if (opacity>=1) cimg_forV(*this,k) (*this)(p,q,0,k) = (T)palette(0,k);
            else cimg_forV(*this,k) (*this)(p,q,0,k) = (T)(palette(0,k)*nopacity + (*this)(p,q,0,k)*copacity);
          } else {
            if (opacity>=1) cimg_forV(*this,k) (*this)(p,q,0,k) = (T)0;
            else cimg_forV(*this,k) (*this)(p,q,0,k) = (T)((*this)(p,q,0,k)*copacity);
          }
        } else if (normalized_iteration) {
          const float
            normz = (float)cimg::abs(zr*zr+zi*zi),
            niter = (float)(iter + 1 - std::log(std::log(normz))/ln2);
          if (palette) {
            if (opacity>=1) cimg_forV(*this,k) (*this)(p,q,0,k) = (T)palette.linear_pix1d(niter,k);
            else cimg_forV(*this,k) (*this)(p,q,0,k) = (T)(palette.linear_pix1d(niter,k)*nopacity + (*this)(p,q,0,k)*copacity);
          } else {
            if (opacity>=1) cimg_forV(*this,k) (*this)(p,q,0,k) = (T)niter;
            else cimg_forV(*this,k) (*this)(p,q,0,k) = (T)(niter*nopacity + (*this)(p,q,0,k)*copacity);
          }
        } else {
          if (palette) {
            if (opacity>=1) cimg_forV(*this,k) (*this)(p,q,0,k) = (T)palette.pix1d(iter,k);
            else cimg_forV(*this,k) (*this)(p,q,0,k) = (T)(palette(iter,k)*nopacity + (*this)(p,q,0,k)*copacity);
          } else {
            if (opacity>=1) cimg_forV(*this,k) (*this)(p,q,0,k) = (T)iter;
            else cimg_forV(*this,k) (*this)(p,q,0,k) = (T)(iter*nopacity + (*this)(p,q,0,k)*copacity);
          }
        }
      }
      return *this;
    }

    //! Draw a quadratic Mandelbrot or Julia fractal set, computed using the Escape Time Algorithm.
    template<typename tc>
    CImg<T>& draw_mandelbrot(const CImg<tc>& color_palette,
                             const double z0r=-2, const double z0i=-2, const double z1r=2, const double z1i=2,
                             const unsigned int itermax=255,
                             const bool normalized_iteration=false,
                             const bool julia_set=false,
                             const double paramr=0, const double parami=0,
                             const float opacity=1.0f) {
      return draw_mandelbrot(0,0,width-1,height-1,color_palette,z0r,z0i,z1r,z1i,itermax,normalized_iteration,julia_set,paramr,parami,opacity);
    }

    //! Draw a 1D gaussian function in the instance image.
    /**
       \param xc = X-coordinate of the gaussian center.
       \param sigma = Standard variation of the gaussian distribution.
       \param color = array of dimv() values of type \c T, defining the drawing color.
       \param opacity = opacity of the drawing.
    **/
    template<typename tc>
      CImg<T>& draw_gaussian(const float xc, const double sigma, const tc *const color, const float opacity=1.0f) {
      if (!is_empty()) {
        if (!color) throw CImgArgumentException("CImg<%s>::draw_gaussian() : Specified color is (null)",pixel_type());
        const double sigma2 = 2*sigma*sigma;
        const float nopacity = cimg::abs(opacity), copacity = 1-cimg::max(opacity,0.0f);
        const unsigned int whz = width*height*depth;
        const tc *col = color;
        cimg_forX(*this,x) {
          const float dx = (x-xc);
          const double val = std::exp( -dx*dx/sigma2 );
          T *ptrd = ptr(x,0,0,0);
          if (opacity>=1) cimg_forV(*this,k) { *ptrd = (T)(val*(*col++)); ptrd+=whz; }
          else cimg_forV(*this,k) { *ptrd = (T)(nopacity*val*(*col++) + *ptrd*copacity); ptrd+=whz; }
          col-=dim;
        }
      }
      return *this;
    }

    template<typename tc>
      CImg<T>& draw_gaussian(const float xc, const double sigma, const CImg<tc>& color, const float opacity=1.0f) {
      return draw_gaussian(xc,sigma,color.data,opacity);
    }

    //! Draw an anisotropic 2D gaussian function in the instance image.
    /**
       \param xc = X-coordinate of the gaussian center.
       \param yc = Y-coordinate of the gaussian center.
       \param tensor = 2x2 covariance matrix.
       \param color = array of dimv() values of type \c T, defining the drawing color.
       \param opacity = opacity of the drawing.
    **/
    template<typename t, typename tc>
      CImg<T>& draw_gaussian(const float xc, const float yc, const CImg<t>& tensor,
                             const tc *const color, const float opacity=1.0f) {
      typedef typename cimg::superset<t,float>::type ftype;
      if (!is_empty()) {
        if (tensor.width!=2 || tensor.height!=2 || tensor.depth!=1 || tensor.dim!=1)
          throw CImgArgumentException("CImg<%s>::draw_gaussian() : Tensor parameter (%u,%u,%u,%u,%p) is not a 2x2 matrix.",
                                      pixel_type(),tensor.width,tensor.height,tensor.depth,tensor.dim,tensor.data);
        if (!color) throw CImgArgumentException("CImg<%s>::draw_gaussian() : Specified color is (null)",pixel_type());
        const CImg<ftype> invT = tensor.get_invert(), invT2 = (invT*invT)/(-2.0);
        const ftype 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.0f);
        const unsigned int whz = width*height*depth;
        const tc *col = color;
        float dy = -yc;
        cimg_forY(*this,y) {
          float dx = -xc;
          cimg_forX(*this,x) {
            const float val = (float)std::exp(a*dx*dx + b*dx*dy + c*dy*dy);
            T *ptrd = ptr(x,y,0,0);
            if (opacity>=1) cimg_forV(*this,k) { *ptrd = (T)(val*(*col++)); ptrd+=whz; }
            else cimg_forV(*this,k) { *ptrd = (T)(nopacity*val*(*col++) + *ptrd*copacity); ptrd+=whz; }
            col-=dim;
            ++dx;
          }
          ++dy;
        }
      }
      return *this;
    }

    template<typename t, typename tc>
      CImg<T>& draw_gaussian(const float xc, const float yc, const CImg<t>& tensor,
                             const CImg<tc>& color, const float opacity=1.0f) {
      return draw_gaussian(xc,yc,tensor,color.data,opacity);
    }

    //! Draw an isotropic 2D gaussian function in the instance image
    /**
       \param xc = X-coordinate of the gaussian center.
       \param yc = Y-coordinate of the gaussian center.
       \param sigma = standard variation of the gaussian distribution.
       \param color = array of dimv() values of type \c T, defining the drawing color.
       \param opacity = opacity of the drawing.
    **/
    template<typename tc>
      CImg<T>& draw_gaussian(const float xc, const float yc, const float sigma, const tc *const color, const float opacity=1.0f) {
      return draw_gaussian(xc,yc,CImg<float>::diagonal(sigma,sigma),color,opacity);
    }

    template<typename tc>
      CImg<T>& draw_gaussian(const float xc, const float yc, const float sigma, const CImg<tc>& color, const float opacity=1.0f) {
      return draw_gaussian(xc,yc,sigma,color.data,opacity);
    }

    //! Draw an anisotropic 3D gaussian function in the instance image.
    /**
       \param xc = X-coordinate of the gaussian center.
       \param yc = Y-coordinate of the gaussian center.
       \param zc = Z-coordinate of the gaussian center.
       \param tensor = 3x3 covariance matrix.
       \param color = array of dimv() values of type \c T, defining the drawing color.
       \param opacity = opacity of the drawing.
    **/
    template<typename t, typename tc>
      CImg<T>& draw_gaussian(const float xc, const float yc, const float zc, const CImg<t>& tensor,
                             const tc *const color, const float opacity=1.0f) {
      if (!is_empty()) {
        if (tensor.width!=3 || tensor.height!=3 || tensor.depth!=1 || tensor.dim!=1)
          throw CImgArgumentException("CImg<%s>::draw_gaussian() : Tensor parameter (%u,%u,%u,%u,%p) is not a 3x3 matrix.",
                                      pixel_type(),tensor.width,tensor.height,tensor.depth,tensor.dim,tensor.data);
        const CImg<t> invT = tensor.get_invert(), invT2 = (invT*invT)/(-2.0);
        const t a=invT(0,0), b=2*invT(1,0), c=2*invT(2,0), d=invT(1,1), e=2*invT(2,1), f=invT(2,2);
        const float nopacity = cimg::abs(opacity), copacity = 1-cimg::max(opacity,0.0f);
        const unsigned int whz = width*height*depth;
        const tc *col = color;
        cimg_forXYZ(*this,x,y,z) {
          const float dx = (x-xc), dy = (y-yc), dz = (z-zc);
          const double val = std::exp(a*dx*dx + b*dx*dy + c*dx*dz + d*dy*dy + e*dy*dz + f*dz*dz);
          T *ptrd = ptr(x,y,z,0);
          if (opacity>=1) cimg_forV(*this,k) { *ptrd = (T)(val*(*col++)); ptrd+=whz; }
          else cimg_forV(*this,k) { *ptrd = (T)(nopacity*val*(*col++) + *ptrd*copacity); ptrd+=whz; }
          col-=dim;
        }
      }
      return *this;
    }

    template<typename t, typename tc>
      CImg<T>& draw_gaussian(const float xc, const float yc, const float zc, const CImg<t>& tensor,
                             const CImg<tc>& color, const float opacity=1.0f) {
      return draw_gaussian(xc,yc,zc,tensor,color.data,opacity);
    }

    //! Draw an isotropic 3D gaussian function in the instance image
   /**
       \param xc = X-coordinate of the gaussian center.
       \param yc = Y-coordinate of the gaussian center.
       \param zc = Z-coordinate of the gaussian center.
       \param sigma = standard variation of the gaussian distribution.
       \param color = array of dimv() values of type \c T, defining the drawing color.
       \param opacity = opacity of the drawing.
    **/
    template<typename tc>
      CImg<T>& draw_gaussian(const float xc, const float yc, const float zc,
                             const double sigma, const tc *const color, const float opacity=1.0f) {
      return draw_gaussian(xc,yc,zc,CImg<float>::diagonal(sigma,sigma,sigma),color,opacity);
    }

    template<typename tc>
      CImg<T>& draw_gaussian(const float xc, const float yc, const float zc,
                             const double sigma, const CImg<tc>& color, const float opacity=1.0f) {
      return draw_gaussian(xc,yc,zc,sigma,color.data,opacity);
    }

    //! Draw a 3D object in the instance image
    /**
       \param X = X-coordinate of the 3d object position
       \param Y = Y-coordinate of the 3d object position
       \param Z = Z-coordinate of the 3d object position
       \param points = Image N*3 describing 3D point coordinates
       \param primitives = List of P primitives
       \param colors = List of P color (or textures)
       \param opacities = Image of P opacities
       \param render_type = Render type (0=Points, 1=Lines, 2=Faces (no light), 3=Faces (flat), 4=Faces(Gouraud)
       \param double_sided = Tell if object faces have two sides or are oriented.
       \param focale = length of the focale
       \param lightx = X-coordinate of the light
       \param lighty = Y-coordinate of the light
       \param lightz = Z-coordinate of the light
       \param specular_shine = Shininess of the object
    **/
    template<typename tp, typename tf, typename tc, typename to>
      CImg<T>& draw_object3d(const float X, const float Y, const float Z,
                             const CImg<tp>& points, const CImgList<tf>& primitives,
                             const CImgList<tc>& colors, const CImgList<to>& opacities,
                             const unsigned int render_type=4,
                             const bool double_sided=false, const float focale=500,
                             const float lightx=0, const float lighty=0, const float lightz=-5000,
                             const float specular_light=0.2f, const float specular_shine=0.1f) {

      static CImg<float> light_texture;
      if (is_empty() || !points || !primitives) return *this;
      if (!colors || !opacities)
        throw CImgArgumentException("CImg<%s>::draw_object3d() : Undefined colors or opacities",pixel_type());

      if (points.height<3)
        return draw_object3d(X,Y,Z,points.get_resize(-100,3,1,1,0),primitives,colors,opacities,
                             render_type,double_sided,focale,lightx,lighty,lightz,specular_light,specular_shine);

      const float
        nspec = 1-(specular_light<0?0:(specular_light>1?1:specular_light)),
        nspec2 = 1+(specular_shine<0?0:specular_shine),
        nsl1 = (nspec2-1)/cimg::sqr(nspec-1),
        nsl2 = (1-2*nsl1*nspec),
        nsl3 = nspec2-nsl1-nsl2;

      // Create light texture for phong-like rendering
      if (render_type==5) {
        if (colors.size>primitives.size) light_texture.assign(colors[primitives.size])/=255;
        else {
          static float olightx = 0, olighty = 0, olightz = 0, ospecular_shine = 0;
          if (!light_texture || lightx!=olightx || lighty!=olighty || lightz!=olightz || specular_shine!=ospecular_shine) {
            light_texture.assign(512,512);
            const float white[] = { 1.0f },
              dlx = lightx-X, dly = lighty-Y, dlz = lightz-Z,
                nl = (float)std::sqrt(dlx*dlx+dly*dly+dlz*dlz),
                nlx = light_texture.width/2*(1+dlx/nl),
                nly = light_texture.height/2*(1+dly/nl);
              light_texture.draw_gaussian(nlx,nly,light_texture.width/3.0f,white);
              cimg_forXY(light_texture,x,y) {
                const float factor = light_texture(x,y);
                if (factor>nspec) light_texture(x,y) = cimg::min(2.0f,nsl1*factor*factor+nsl2*factor+nsl3);
              }
              olightx = lightx; olighty = lighty; olightz = lightz; ospecular_shine = specular_shine;
          }
        }
      }

      // Compute 3D to 2D projection
      CImg<float> projections(points.width,2);
      cimg_forX(points,l) {
        const float
          x = (float)points(l,0),
          y = (float)points(l,1),
          z = (float)points(l,2);
        const float projectedz = z + Z + focale;
        projections(l,1) = Y + focale*y/projectedz;
        projections(l,0) = X + focale*x/projectedz;
      }

      // Compute and sort visible primitives
      CImg<unsigned int> visibles(primitives.size);
      CImg<float> zrange(primitives.size);
      unsigned int nb_visibles = 0;
      const float zmin = -focale+1.5f;
      { cimglist_for(primitives,l) {
        const CImg<tf>& primitive = primitives[l];
        switch (primitive.size()) {

        case 1: { // Point
          const unsigned int i0 = (unsigned int)primitive(0);
          const float x0 = projections(i0,0), y0 = projections(i0,1), z0 = (float)(Z+points(i0,2));
          if (z0>zmin && x0>=0 && x0<width && y0>=0 && y0<height) {
            visibles(nb_visibles) = (unsigned int)l;
            zrange(nb_visibles++) = z0;
          }
        } break;
        case 5: { // Sphere
          const unsigned int
            i0 = (unsigned int)primitive(0),
            i1 = (unsigned int)primitive(1),
            i2 = (unsigned int)primitive(2);
          const float x0 = projections(i0,0), y0 = projections(i0,1), z0 = (float)(Z+points(i0,2));
          int radius;
          if (i2) radius = (int)(i2*focale/(z0+focale));
          else {
            const float x1 = projections(i1,0), y1 = projections(i1,1);
            const int deltax = (int)(x1-x0), deltay = (int)(y1-y0);
            radius = (int)std::sqrt((float)(deltax*deltax + deltay*deltay));
          }
          if (z0>zmin && x0+radius>=0 && x0-radius<width && y0+radius>=0 && y0-radius<height) {
            visibles(nb_visibles) = (unsigned int)l;
            zrange(nb_visibles++) = z0;
          }
        } break;
        case 2: // Line
        case 6: {
          const unsigned int
            i0 = (unsigned int)primitive(0),
            i1 = (unsigned int)primitive(1);
          const float
            x0 = projections(i0,0), y0 = projections(i0,1), z0 = (float)(Z+points(i0,2)),
            x1 = projections(i1,0), y1 = projections(i1,1), z1 = (float)(Z+points(i1,2));
          float xm, xM, ym, yM;
          if (x0<x1) { xm = x0; xM = x1; } else { xm = x1; xM = x0; }
          if (y0<y1) { ym = y0; yM = y1; } else { ym = y1; yM = y0; }
          if (z0>zmin && z1>zmin && xM>=0 && xm<width && yM>=0 && ym<height) {
            visibles(nb_visibles) = (unsigned int)l;
            zrange(nb_visibles++) = 0.5f*(z0+z1);
          }
        } break;
        case 3:  // Triangle
        case 9: {
          const unsigned int
            i0 = (unsigned int)primitive(0),
            i1 = (unsigned int)primitive(1),
            i2 = (unsigned int)primitive(2);
          const float
            x0 = projections(i0,0), y0 = projections(i0,1), z0 = (float)(Z+points(i0,2)),
            x1 = projections(i1,0), y1 = projections(i1,1), z1 = (float)(Z+points(i1,2)),
            x2 = projections(i2,0), y2 = projections(i2,1), z2 = (float)(Z+points(i2,2));
          float xm, xM, ym, yM;
          if (x0<x1) { xm = x0; xM = x1; } else { xm = x1; xM = x0; }
          if (x2<xm) xm = x2;
          if (x2>xM) xM = x2;
          if (y0<y1) { ym = y0; yM = y1; } else { ym = y1; yM = y0; }
          if (y2<ym) ym = y2;
          if (y2>yM) yM = y2;
          if (z0>zmin && z1>zmin && z2>zmin && xM>=0 && xm<width && yM>=0 && ym<height) {
            const float d = (x1-x0)*(y2-y0)-(x2-x0)*(y1-y0);
            if (double_sided || d<0) {
              visibles(nb_visibles) = (unsigned int)l;
              zrange(nb_visibles++) = (z0+z1+z2)/3;
            }
          }
        } break;
        case 4: // Rectangle
        case 12: {
          const unsigned int
            i0 = (unsigned int)primitive(0),
            i1 = (unsigned int)primitive(1),
            i2 = (unsigned int)primitive(2),
            i3 = (unsigned int)primitive(3);
          const float
            x0 = projections(i0,0), y0 = projections(i0,1), z0 = (float)(Z+points(i0,2)),
            x1 = projections(i1,0), y1 = projections(i1,1), z1 = (float)(Z+points(i1,2)),
            x2 = projections(i2,0), y2 = projections(i2,1), z2 = (float)(Z+points(i2,2)),
            x3 = projections(i3,0), y3 = projections(i3,1), z3 = (float)(Z+points(i3,2));
          float xm, xM, ym, yM;
          if (x0<x1) { xm = x0; xM = x1; } else { xm = x1; xM = x0; }
          if (x2<xm) xm = x2;
          if (x2>xM) xM = x2;
          if (x3<xm) xm = x3;
          if (x3>xM) xM = x3;
          if (y0<y1) { ym = y0; yM = y1; } else { ym = y1; yM = y0; }
          if (y2<ym) ym = y2;
          if (y2>yM) yM = y2;
          if (y3<ym) ym = y3;
          if (y3>yM) yM = y3;
          if (z0>zmin && z1>zmin && z2>zmin && z3>zmin && xM>=0 && xm<width && yM>=0 && ym<height) {
            const float d = (x1-x0)*(y2-y0)-(x2-x0)*(y1-y0);
            if (double_sided || d<0) {
              visibles(nb_visibles) = (unsigned int)l;
              zrange(nb_visibles++) = (z0+z1+z2+z3)/4;
            }
          }
        } break;
        default:
          throw CImgArgumentException("CImg<%s>::draw_object3d() : Primitive %u is invalid (size = %u, can be 1,2,3,4,5,6,9 or 12)",
                                      pixel_type(),l,primitive.size());
        }}
      }
      if (nb_visibles<=0) return *this;
      CImg<unsigned int> permutations;
      CImg<float>(zrange.data,nb_visibles,1,1,1,true).sort(permutations,false);

      // Compute light properties
      CImg<float> lightprops;
      switch (render_type) {
      case 3: { // Flat Shading
        lightprops.assign(nb_visibles);
        cimg_forX(lightprops,l) {
          const CImg<tf>& primitive = primitives(visibles(permutations(l)));
          const unsigned int psize = primitive.size();
          if (psize==3 || psize==4 || psize==9 || psize==12) {
            const unsigned int
              i0 = (unsigned int)primitive(0),
              i1 = (unsigned int)primitive(1),
              i2 = (unsigned int)primitive(2);
            const float
              x0 = (float)points(i0,0), y0 = (float)points(i0,1), z0 = (float)points(i0,2),
              x1 = (float)points(i1,0), y1 = (float)points(i1,1), z1 = (float)points(i1,2),
              x2 = (float)points(i2,0), y2 = (float)points(i2,1), z2 = (float)points(i2,2),
              dx1 = x1-x0, dy1 = y1-y0, dz1 = z1-z0,
              dx2 = x2-x0, dy2 = y2-y0, dz2 = z2-z0,
              nx = dy1*dz2-dz1*dy2,
              ny = dz1*dx2-dx1*dz2,
              nz = dx1*dy2-dy1*dx2,
              norm = (float)std::sqrt(1e-5f+nx*nx+ny*ny+nz*nz),
              lx = X+(x0+x1+x2)/3-lightx,
              ly = Y+(y0+y1+y2)/3-lighty,
              lz = Z+(z0+z1+z2)/3-lightz,
              nl = (float)std::sqrt(1e-5f+lx*lx+ly*ly+lz*lz),
              factor = cimg::max(cimg::abs(-lx*nx-ly*ny-lz*nz)/(norm*nl),0.0f);
            lightprops[l] = factor<=nspec?factor:(nsl1*factor*factor + nsl2*factor + nsl3);
          } else lightprops[l] = 1.0f;
        }
      } break;

      case 4: // Gouraud Shading
      case 5: { // Phong-Shading
        CImg<float> points_normals(points.width,double_sided?7:3,1,1,0);
        for (unsigned int l=0; l<nb_visibles; ++l) {
          const CImg<tf>& primitive = primitives[visibles(l)];
          const unsigned int psize = primitive.size();
          const bool
            triangle_flag  = (psize==3) || (psize==9),
            rectangle_flag = (psize==4) || (psize==12);
          if (triangle_flag || rectangle_flag) {
            const unsigned int
              i0 = (unsigned int)primitive(0),
              i1 = (unsigned int)primitive(1),
              i2 = (unsigned int)primitive(2),
              i3 = rectangle_flag?(unsigned int)primitive(3):0;
            const float
              x0 = (float)points(i0,0), y0 = (float)points(i0,1), z0 = (float)points(i0,2),
              x1 = (float)points(i1,0), y1 = (float)points(i1,1), z1 = (float)points(i1,2),
              x2 = (float)points(i2,0), y2 = (float)points(i2,1), z2 = (float)points(i2,2),
              dx1 = x1-x0, dy1 = y1-y0, dz1 = z1-z0,
              dx2 = x2-x0, dy2 = y2-y0, dz2 = z2-z0,
              nnx = dy1*dz2-dz1*dy2,
              nny = dz1*dx2-dx1*dz2,
              nnz = dx1*dy2-dy1*dx2,
              norm = 1e-5f + (float)std::sqrt(nnx*nnx+nny*nny+nnz*nnz),
              nx = nnx/norm,
              ny = nny/norm,
              nz = nnz/norm;
            if (double_sided) {
              unsigned int ind = nz>0?3U:0U;
              const float incr = nz>0?-1.0f:1.0f;
              points_normals(i0,  ind)+=nx; points_normals(i1,ind)+=nx; points_normals(i2,ind)+=nx;
              points_normals(i0,++ind)+=ny; points_normals(i1,ind)+=ny; points_normals(i2,ind)+=ny;
              points_normals(i0,++ind)+=nz; points_normals(i1,ind)+=nz; points_normals(i2,ind)+=nz;
              points_normals(i0,6)+=incr; points_normals(i1,6)+=incr; points_normals(i2,6)+=incr;
              if (rectangle_flag) {
                points_normals(i3,ind)+=nz; points_normals(i3,--ind)+=ny; points_normals(i3,--ind)+=nz; points_normals(i3,6)+=incr;
              }
            } else {
              points_normals(i0,0)+=nx; points_normals(i0,1)+=ny; points_normals(i0,2)+=nz;
              points_normals(i1,0)+=nx; points_normals(i1,1)+=ny; points_normals(i1,2)+=nz;
              points_normals(i2,0)+=nx; points_normals(i2,1)+=ny; points_normals(i2,2)+=nz;
              if (rectangle_flag) { points_normals(i3,0)+=nx; points_normals(i3,1)+=ny; points_normals(i3,2)+=nz; }
            }
          }
        }

        if (double_sided) cimg_forX(points_normals,l) if (points_normals(l,6)<0) {
          points_normals(l,0) = -points_normals(l,3);
          points_normals(l,1) = -points_normals(l,4);
          points_normals(l,2) = -points_normals(l,5);
        }

        if (render_type==4) {
          lightprops.assign(points.width);
          cimg_forX(points,ll) {
            const float
              nx = points_normals(ll,0),
              ny = points_normals(ll,1),
              nz = points_normals(ll,2),
              norm = (float)std::sqrt(1e-5f+nx*nx+ny*ny+nz*nz),
              lx = (float)(X+points(ll,0)-lightx),
              ly = (float)(Y+points(ll,1)-lighty),
              lz = (float)(Z+points(ll,2)-lightz),
              nl = (float)std::sqrt(1e-5f+lx*lx+ly*ly+lz*lz),
              factor = cimg::max((-lx*nx-ly*ny-lz*nz)/(norm*nl),0.0f);
            lightprops[ll] = factor<=nspec?factor:(nsl1*factor*factor + nsl2*factor + nsl3);
          }
        } else {
          const unsigned int
            lw2 = light_texture.width/2-1,
            lh2 = light_texture.height/2-1;
          lightprops.assign(points.width,2);
          cimg_forX(points,ll) {
            const float
              nx = points_normals(ll,0),
              ny = points_normals(ll,1),
              nz = points_normals(ll,2),
              norm = (float)std::sqrt(1e-5f+nx*nx+ny*ny+nz*nz),
              nnx = nx/norm,
              nny = ny/norm;
            lightprops(ll,0) = lw2*(1+nnx);
            lightprops(ll,1) = lh2*(1+nny);
          }
        }
      } break;
      }

      // Draw visible primitives
      const unsigned int opacsize = opacities.size;
      { 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 = colors[n_primitive%colors.size];
        const CImg<to>& opacity = opacities[n_primitive%opacsize];
        const float opac = opacity.size()?(float)opacity(0):1.0f;

        switch (primitive.size()) {
        case 1: { // Colored point or sprite
          const unsigned int n0 = (unsigned int)primitive[0];
          const int x0 = (int)projections(n0,0), y0 = (int)projections(n0,1);
          if (color.size()==dim) draw_point(x0,y0,color,opac);
          else {
            const float z = Z + points(n0,2);
            const int
              factor = (int)(focale*100/(z+focale)),
              sw = color.width*factor/200,
              sh = color.height*factor/200;
            if (x0+sw>=0 && x0-sw<dimx() && y0+sh>=0 && y0-sh<dimy()) {
              const CImg<T> sprite = color.get_resize(-factor,-factor,1,-100,render_type<=3?1:3);
              if (opacity.width==color.width && opacity.height==color.height)
                draw_image(sprite,opacity.get_resize(sprite.width,sprite.height,1,sprite.dim,1),x0-sw,y0-sh,0,0);
              else draw_image(sprite,x0-sw,y0-sh,0,0,opac);
            }
          }
        } 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);
          if (render_type) draw_line(x0,y0,x1,y1,color,opac);
          else draw_point(x0,y0,color,opac).draw_point(x1,y1,color,opac);
        } break;
        case 5: { // Colored sphere
          const unsigned int
            n0 = (unsigned int)primitive[0],
            n1 = (unsigned int)primitive[1],
            n2 = (unsigned int)primitive[2];
          const int
            x0 = (int)projections(n0,0), y0 = (int)projections(n0,1);
          int radius;
          if (n2) radius = (int)(n2*focale/(Z+points(n0,2)+focale));
          else {
            const int
              x1 = (int)projections(n1,0), y1 = (int)projections(n1,1),
              deltax = x1-x0, deltay = y1-y0;
            radius = (int)std::sqrt((float)(deltax*deltax + deltay*deltay));
          }
          switch (render_type) {
          case 0:
            draw_point(x0,y0,color,opac);
            break;
          case 1:
            draw_circle(x0,y0,radius,color,opac,~0U);
            break;
          default:
            draw_circle(x0,y0,radius,color,opac);
            break;
          }
        } break;
        case 6: { // Textured line
          const unsigned int
            n0 = (unsigned int)primitive[0],
            n1 = (unsigned int)primitive[1],
            tx0 = (unsigned int)primitive[2],
            ty0 = (unsigned int)primitive[3],
            tx1 = (unsigned int)primitive[4],
            ty1 = (unsigned int)primitive[5];
          const int
            x0 = (int)projections(n0,0), y0 = (int)projections(n0,1),
            x1 = (int)projections(n1,0), y1 = (int)projections(n1,1);
          const float
            z0 = points(n0,2) + Z + focale,
            z1 = points(n1,2) + Z + focale;
          if (render_type) draw_line(x0,y0,z0,x1,y1,z1,color,tx0,ty0,tx1,ty1,opac);
          else draw_point(x0,y0,color.get_vector_at(tx0,ty0),opac).
                 draw_point(x1,y1,color.get_vector_at(tx1,ty1),opac);
        } 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);
          switch(render_type) {
          case 0:
            draw_point(x0,y0,color,opac).draw_point(x1,y1,color,opac).draw_point(x2,y2,color,opac);
            break;
          case 1:
            draw_line(x0,y0,x1,y1,color,opac).draw_line(x0,y0,x2,y2,color,opac).
              draw_line(x1,y1,x2,y2,color,opac);
            break;
          case 2:
            draw_triangle(x0,y0,x1,y1,x2,y2,color,opac);
            break;
          case 3:
            _draw_triangle(x0,y0,x1,y1,x2,y2,color.data,opac,lightprops(l));
            break;
          case 4:
            draw_triangle(x0,y0,x1,y1,x2,y2,color,lightprops(n0),lightprops(n1),lightprops(n2),opac);
            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);
            draw_triangle(x0,y0,x1,y1,x2,y2,color,light_texture,lx0,ly0,lx1,ly1,lx2,ly2,opac);
            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);
          switch(render_type) {
          case 0:
            draw_point(x0,y0,color,opac).draw_point(x1,y1,color,opac).
              draw_point(x2,y2,color,opac).draw_point(x3,y3,color,opac);
            break;
          case 1:
            draw_line(x0,y0,x1,y1,color,opac).draw_line(x1,y1,x2,y2,color,opac).
              draw_line(x2,y2,x3,y3,color,opac).draw_line(x3,y3,x0,y0,color,opac);
            break;
          case 2:
            draw_triangle(x0,y0,x1,y1,x2,y2,color,opac).draw_triangle(x0,y0,x2,y2,x3,y3,color,opac);
            break;
          case 3:
            _draw_triangle(x0,y0,x1,y1,x2,y2,color.data,opac,lightprops(l)).
              _draw_triangle(x0,y0,x2,y2,x3,y3,color.data,opac,lightprops(l));
            break;
          case 4: {
            const float
              lightprop0 = lightprops(n0), lightprop1 = lightprops(n1),
              lightprop2 = lightprops(n2), lightprop3 = lightprops(n3);
            draw_triangle(x0,y0,x1,y1,x2,y2,color,lightprop0,lightprop1,lightprop2,opac).
              draw_triangle(x0,y0,x2,y2,x3,y3,color,lightprop0,lightprop2,lightprop3,opac);
          } 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);
            draw_triangle(x0,y0,x1,y1,x2,y2,color,light_texture,lx0,ly0,lx1,ly1,lx2,ly2,opac).
              draw_triangle(x0,y0,x2,y2,x3,y3,color,light_texture,lx0,ly0,lx2,ly2,lx3,ly3,opac);
          } break;
          }
        } break;
        case 9: { // Textured triangle
          const unsigned int
            n0 = (unsigned int)primitive[0],
            n1 = (unsigned int)primitive[1],
            n2 = (unsigned int)primitive[2],
            tx0 = (unsigned int)primitive[3],
            ty0 = (unsigned int)primitive[4],
            tx1 = (unsigned int)primitive[5],
            ty1 = (unsigned int)primitive[6],
            tx2 = (unsigned int)primitive[7],
            ty2 = (unsigned int)primitive[8];
          const int
            x0 = (int)projections(n0,0), y0 = (int)projections(n0,1),
            x1 = (int)projections(n1,0), y1 = (int)projections(n1,1),
            x2 = (int)projections(n2,0), y2 = (int)projections(n2,1);
          const float
            z0 = points(n0,2) + Z + focale,
            z1 = points(n1,2) + Z + focale,
            z2 = points(n2,2) + Z + focale;
          switch(render_type) {
          case 0:
            draw_point(x0,y0,color.get_vector_at(tx0,ty0),opac).
              draw_point(x1,y1,color.get_vector_at(tx1,ty1),opac).
              draw_point(x2,y2,color.get_vector_at(tx2,ty2),opac);
            break;
          case 1:
            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);
            break;
          case 2:
            draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,opac);
            break;
          case 3:
            draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,opac,lightprops(l));
            break;
          case 4:
            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);
            break;
          case 5:
            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);
            break;
          }
        } break;
        case 12: { // Textured rectangle
          const unsigned int
            n0 = (unsigned int)primitive[0],
            n1 = (unsigned int)primitive[1],
            n2 = (unsigned int)primitive[2],
            n3 = (unsigned int)primitive[3],
            tx0 = (unsigned int)primitive[4],
            ty0 = (unsigned int)primitive[5],
            tx1 = (unsigned int)primitive[6],
            ty1 = (unsigned int)primitive[7],
            tx2 = (unsigned int)primitive[8],
            ty2 = (unsigned int)primitive[9],
            tx3 = (unsigned int)primitive[10],
            ty3 = (unsigned int)primitive[11];
          const int
            x0 = (int)projections(n0,0), y0 = (int)projections(n0,1),
            x1 = (int)projections(n1,0), y1 = (int)projections(n1,1),
            x2 = (int)projections(n2,0), y2 = (int)projections(n2,1),
            x3 = (int)projections(n3,0), y3 = (int)projections(n3,1);
          const float
            z0 = points(n0,2) + Z + focale,
            z1 = points(n1,2) + Z + focale,
            z2 = points(n2,2) + Z + focale,
            z3 = points(n3,2) + Z + focale;
          switch(render_type) {
          case 0:
            draw_point(x0,y0,color.get_vector_at(tx0,ty0),opac).
              draw_point(x1,y1,color.get_vector_at(tx1,ty1),opac).
              draw_point(x2,y2,color.get_vector_at(tx2,ty2),opac).
              draw_point(x3,y3,color.get_vector_at(tx3,ty3),opac);
            break;
          case 1:
            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);
            break;
          case 2:
            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);
            break;
          case 3:
            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));
            break;
          case 4: {
            const float
              lightprop0 = lightprops(n0), lightprop1 = lightprops(n1),
              lightprop2 = lightprops(n2), lightprop3 = lightprops(n3);
            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);
          } 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);
            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);
          } break;
          }
        } break;
        }
      }
      }
      return *this;
    }

    //! Draw a 3D object in the instance image
    template<typename tp, typename tf, typename tc, typename to>
      CImg<T>& draw_object3d(const float X, const float Y, const float Z,
                             const CImgList<tp>& points, const CImgList<tf>& primitives,
                             const CImgList<tc>& colors, const CImgList<to>& opacities,
                             const unsigned int render_type=4,
                             const bool double_sided=false, const float focale=500,
                             const float lightx=0, const float lighty=0, const float lightz=-5000,
                             const float specular_light=0.2f, const float specular_shine=0.1f) {
      if (!points) return *this;
      CImg<tp> npoints(points.size,3,1,1,0);
      tp *ptrX = npoints.data, *ptrY = npoints.ptr(0,1), *ptrZ = npoints.ptr(0,2);
      cimg_forX(npoints,l) {
        const CImg<tp>& point = points[l];
        const unsigned int siz = point.size();
        if (!siz)
          throw CImgArgumentException("CImg<%s>::draw_object3d() : Given points (size=%u) contains a null element at "
                                      "position %u.",pixel_type(),points.size,l);
        *(ptrZ++) = (siz>2)?point(2):0;
        *(ptrY++) = (siz>1)?point(1):0;
        *(ptrX++) = point(0);
      }
      return draw_object3d(X,Y,Z,npoints,primitives,colors,opacities,
                           render_type,double_sided,focale,lightx,lighty,lightz,specular_light,specular_shine);
    }

    //! Draw a 3D object in the instance image
    template<typename tp, typename tf, typename tc, typename to>
      CImg<T>& draw_object3d(const float X, const float Y, const float Z,
                             const CImg<tp>& points, const CImgList<tf>& primitives,
                             const CImgList<tc>& colors, const CImg<to>& opacities,
                             const unsigned int render_type=4,
                             const bool double_sided=false, const float focale=500,
                             const float lightx=0, const float lighty=0, const float lightz=-5000,
                             const float specular_light=0.2f, const float specular_shine=0.1f) {
      CImgList<to> nopacities(opacities.size(),1);
      cimglist_for(nopacities,l) nopacities(l,0) = opacities(l);
      return draw_object3d(X,Y,Z,points,primitives,colors,nopacities,
                           render_type,double_sided,focale,lightx,lighty,lightz,specular_light,specular_shine);
    }

    //! Draw a 3D object in the instance image
    template<typename tp, typename tf, typename tc, typename to>
      CImg<T>& draw_object3d(const float X, const float Y, const float Z,
                             const CImgList<tp>& points, const CImgList<tf>& primitives,
                             const CImgList<tc>& colors, const CImg<to>& opacities,
                             const unsigned int render_type=4,
                             const bool double_sided=false, const float focale=500,
                             const float lightx=0, const float lighty=0, const float lightz=-5000,
                             const float specular_light=0.2f, const float specular_shine=0.1f) {
      CImgList<to> nopacities(opacities.size(),1);
      { cimglist_for(nopacities,l) nopacities(l,0) = opacities(l); }
      if (!points) return *this;
      CImg<tp> npoints(points.size,3,1,1,0);
      tp *ptrX = npoints.data, *ptrY = npoints.ptr(0,1), *ptrZ = npoints.ptr(0,2);
      cimg_forX(npoints,l) {
        const CImg<tp>& point = points[l];
        const unsigned int siz = point.size();
        if (!siz)
          throw CImgArgumentException("CImg<%s>::draw_object3d() : Given points (size=%u) contains a null element at "
                                      "position %u.",pixel_type(),points.size,l);
        *(ptrZ++) = (siz>2)?point(2):0;
        *(ptrY++) = (siz>1)?point(1):0;
        *(ptrX++) = point(0);
      }
      return draw_object3d(X,Y,Z,npoints,primitives,colors,nopacities,
                           render_type,double_sided,focale,lightx,lighty,lightz,specular_light,specular_shine);
    }

    //! Draw a 3D object in the instance image
    template<typename tp, typename tf, typename tc>
      CImg<T>& draw_object3d(const float X, const float Y, const float Z,
                             const tp& points, const CImgList<tf>& primitives,
                             const CImgList<tc>& colors,
                             const unsigned int render_type=4,
                             const bool double_sided=false, const float focale=500,
                             const float lightx=0, const float lighty=0, const float lightz=-5000,
                             const float specular_light=0.2f, const float specular_shine=0.1f,
                             const float opacity=1.0f) {
      return draw_object3d(X,Y,Z,points,primitives,colors,
                           CImg<float>(primitives.size,1,1,1,opacity),
                           render_type,double_sided,focale,lightx,lighty,lightz,
                           specular_light,specular_shine);
    }

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

    //! Compute the correlation of the instance image by a mask.
    /**
       The correlation of the instance image \p *this by the mask \p mask is defined to be :

       res(x,y,z) = sum_{i,j,k} (*this)(x+i,y+j,z+k)*mask(i,j,k)

       \param mask = the correlation kernel.
       \param cond = the border condition type (0=zero, 1=dirichlet)
       \param weighted_correl = enable local normalization.
    **/
    template<typename t> CImg<typename cimg::superset2<T,t,float>::type>
    get_correlate(const CImg<t>& mask, const unsigned int cond=1, const bool weighted_correl=false) const {
      typedef typename cimg::superset2<T,t,float>::type ftype;
      if (is_empty()) return CImg<ftype>();
      if (!mask || mask.dim!=1)
        throw CImgArgumentException("CImg<%s>::correlate() : Specified mask (%u,%u,%u,%u,%p) is not scalar.",
                                    pixel_type(),mask.width,mask.height,mask.depth,mask.dim,mask.data);
      CImg<ftype> dest(width,height,depth,dim);
      if (cond && mask.width==mask.height && ((mask.depth==1 && mask.width<=5) || (mask.depth==mask.width && mask.width<=3))) {
        // A special optimization is done for 2x2, 3x3, 4x4, 5x5, 2x2x2 and 3x3x3 mask (with cond=1)
        switch (mask.depth) {
        case 3: {
          T I[27] = { 0 };
          cimg_forZV(*this,z,v) cimg_for3x3x3(*this,x,y,z,v,I) dest(x,y,z,v) = (ftype)
            (I[ 0]*mask[ 0] + I[ 1]*mask[ 1] + I[ 2]*mask[ 2] +
             I[ 3]*mask[ 3] + I[ 4]*mask[ 4] + I[ 5]*mask[ 5] +
             I[ 6]*mask[ 6] + I[ 7]*mask[ 7] + I[ 8]*mask[ 8] +
             I[ 9]*mask[ 9] + I[10]*mask[10] + I[11]*mask[11] +
             I[12]*mask[12] + I[13]*mask[13] + I[14]*mask[14] +
             I[15]*mask[15] + I[16]*mask[16] + I[17]*mask[17] +
             I[18]*mask[18] + I[19]*mask[19] + I[20]*mask[20] +
             I[21]*mask[21] + I[22]*mask[22] + I[23]*mask[23] +
             I[24]*mask[24] + I[25]*mask[25] + I[26]*mask[26]);
          if (weighted_correl) cimg_forZV(*this,z,v) cimg_for3x3x3(*this,x,y,z,v,I) {
            const double weight = (double)(I[ 0]*I[ 0] + I[ 1]*I[ 1] + I[ 2]*I[ 2] +
                                           I[ 3]*I[ 3] + I[ 4]*I[ 4] + I[ 5]*I[ 5] +
                                           I[ 6]*I[ 6] + I[ 7]*I[ 7] + I[ 8]*I[ 8] +
                                           I[ 9]*I[ 9] + I[10]*I[10] + I[11]*I[11] +
                                           I[12]*I[12] + I[13]*I[13] + I[14]*I[14] +
                                           I[15]*I[15] + I[16]*I[16] + I[17]*I[17] +
                                           I[18]*I[18] + I[19]*I[19] + I[20]*I[20] +
                                           I[21]*I[21] + I[22]*I[22] + I[23]*I[23] +
                                           I[24]*I[24] + I[25]*I[25] + I[26]*I[26]);
            if (weight>0) dest(x,y,z,v)/=(ftype)std::sqrt(weight);
          }
        } break;
        case 2: {
          T I[8] = { 0 };
          cimg_forZV(*this,z,v) cimg_for2x2x2(*this,x,y,z,v,I) dest(x,y,z,v) = (ftype)
            (I[0]*mask[0] + I[1]*mask[1] +
             I[2]*mask[2] + I[3]*mask[3] +
             I[4]*mask[4] + I[5]*mask[5] +
             I[6]*mask[6] + I[7]*mask[7]);
          if (weighted_correl) cimg_forZV(*this,z,v) cimg_for2x2x2(*this,x,y,z,v,I) {
            const double weight = (double)(I[0]*I[0] + I[1]*I[1] +
                                           I[2]*I[2] + I[3]*I[3] +
                                           I[4]*I[4] + I[5]*I[5] +
                                           I[6]*I[6] + I[7]*I[7]);
            if (weight>0) dest(x,y,z,v)/=(ftype)std::sqrt(weight);
          }
        } break;
        default:
        case 1:
          switch (mask.width) {
          case 6: {
            T I[36] = { 0 };
            cimg_forZV(*this,z,v) cimg_for6x6(*this,x,y,z,v,I) dest(x,y,z,v) = (ftype)
              (I[ 0]*mask[ 0] + I[ 1]*mask[ 1] + I[ 2]*mask[ 2] + I[ 3]*mask[ 3] + I[ 4]*mask[ 4] + I[ 5]*mask[ 5] +
               I[ 6]*mask[ 6] + I[ 7]*mask[ 7] + I[ 8]*mask[ 8] + I[ 9]*mask[ 9] + I[10]*mask[10] + I[11]*mask[11] +
               I[12]*mask[12] + I[13]*mask[13] + I[14]*mask[14] + I[15]*mask[15] + I[16]*mask[16] + I[17]*mask[17] +
               I[18]*mask[18] + I[19]*mask[19] + I[20]*mask[20] + I[21]*mask[21] + I[22]*mask[22] + I[23]*mask[23] +
               I[24]*mask[24] + I[25]*mask[25] + I[26]*mask[26] + I[27]*mask[27] + I[28]*mask[28] + I[29]*mask[29] +
               I[30]*mask[30] + I[31]*mask[31] + I[32]*mask[32] + I[33]*mask[33] + I[34]*mask[34] + I[35]*mask[35]);
            if (weighted_correl) cimg_forZV(*this,z,v) cimg_for5x5(*this,x,y,z,v,I) {
              const double weight = (double)(I[ 0]*I[ 0] + I[ 1]*I[ 1] + I[ 2]*I[ 2] + I[ 3]*I[ 3] + I[ 4]*I[ 4] + I[ 5]*I[ 5] +
                                             I[ 6]*I[ 6] + I[ 7]*I[ 7] + I[ 8]*I[ 8] + I[ 9]*I[ 9] + I[10]*I[10] + I[11]*I[11] +
                                             I[12]*I[12] + I[13]*I[13] + I[14]*I[14] + I[15]*I[15] + I[16]*I[16] + I[17]*I[17] +
                                             I[18]*I[18] + I[19]*I[19] + I[20]*I[20] + I[21]*I[21] + I[22]*I[22] + I[23]*I[23] +
                                             I[24]*I[24] + I[25]*I[25] + I[26]*I[26] + I[27]*I[27] + I[28]*I[28] + I[29]*I[29] +
                                             I[30]*I[30] + I[31]*I[31] + I[32]*I[32] + I[33]*I[33] + I[34]*I[34] + I[35]*I[35]);
              if (weight>0) dest(x,y,z,v)/=(ftype)std::sqrt(weight);
            }
          } break;
          case 5: {
            T I[25] = { 0 };
            cimg_forZV(*this,z,v) cimg_for5x5(*this,x,y,z,v,I) dest(x,y,z,v) = (ftype)
              (I[ 0]*mask[ 0] + I[ 1]*mask[ 1] + I[ 2]*mask[ 2] + I[ 3]*mask[ 3] + I[ 4]*mask[ 4] +
               I[ 5]*mask[ 5] + I[ 6]*mask[ 6] + I[ 7]*mask[ 7] + I[ 8]*mask[ 8] + I[ 9]*mask[ 9] +
               I[10]*mask[10] + I[11]*mask[11] + I[12]*mask[12] + I[13]*mask[13] + I[14]*mask[14] +
               I[15]*mask[15] + I[16]*mask[16] + I[17]*mask[17] + I[18]*mask[18] + I[19]*mask[19] +
               I[20]*mask[20] + I[21]*mask[21] + I[22]*mask[22] + I[23]*mask[23] + I[24]*mask[24]);
            if (weighted_correl) cimg_forZV(*this,z,v) cimg_for5x5(*this,x,y,z,v,I) {
              const double weight = (double)(I[ 0]*I[ 0] + I[ 1]*I[ 1] + I[ 2]*I[ 2] + I[ 3]*I[ 3] + I[ 4]*I[ 4] +
                                             I[ 5]*I[ 5] + I[ 6]*I[ 6] + I[ 7]*I[ 7] + I[ 8]*I[ 8] + I[ 9]*I[ 9] +
                                             I[10]*I[10] + I[11]*I[11] + I[12]*I[12] + I[13]*I[13] + I[14]*I[14] +
                                             I[15]*I[15] + I[16]*I[16] + I[17]*I[17] + I[18]*I[18] + I[19]*I[19] +
                                             I[20]*I[20] + I[21]*I[21] + I[22]*I[22] + I[23]*I[23] + I[24]*I[24]);
              if (weight>0) dest(x,y,z,v)/=(ftype)std::sqrt(weight);
            }
          } break;
          case 4: {
            T I[16] = { 0 };
            cimg_forZV(*this,z,v) cimg_for4x4(*this,x,y,z,v,I) dest(x,y,z,v) = (ftype)
              (I[ 0]*mask[ 0] + I[ 1]*mask[ 1] + I[ 2]*mask[ 2] + I[ 3]*mask[ 3] +
               I[ 4]*mask[ 4] + I[ 5]*mask[ 5] + I[ 6]*mask[ 6] + I[ 7]*mask[ 7] +
               I[ 8]*mask[ 8] + I[ 9]*mask[ 9] + I[10]*mask[10] + I[11]*mask[11] +
               I[12]*mask[12] + I[13]*mask[13] + I[14]*mask[14] + I[15]*mask[15]);
            if (weighted_correl) cimg_forZV(*this,z,v) cimg_for4x4(*this,x,y,z,v,I) {
              const double weight = (double)(I[ 0]*I[ 0] + I[ 1]*I[ 1] + I[ 2]*I[ 2] + I[ 3]*I[ 3] +
                                             I[ 4]*I[ 4] + I[ 5]*I[ 5] + I[ 6]*I[ 6] + I[ 7]*I[ 7] +
                                             I[ 8]*I[ 8] + I[ 9]*I[ 9] + I[10]*I[10] + I[11]*I[11] +
                                             I[12]*I[12] + I[13]*I[13] + I[14]*I[14] + I[15]*I[15]);
              if (weight>0) dest(x,y,z,v)/=(ftype)std::sqrt(weight);
            }
          } break;
          case 3: {
            T I[9] = { 0 };
            cimg_forZV(*this,z,v) cimg_for3x3(*this,x,y,z,v,I) dest(x,y,z,v) = (ftype)
              (I[0]*mask[0] + I[1]*mask[1] + I[2]*mask[2] +
               I[3]*mask[3] + I[4]*mask[4] + I[5]*mask[5] +
               I[6]*mask[6] + I[7]*mask[7] + I[8]*mask[8]);
            if (weighted_correl) cimg_forZV(*this,z,v) cimg_for3x3(*this,x,y,z,v,I) {
              const double weight = (double)(I[0]*I[0] + I[1]*I[1] + I[2]*I[2] +
                                             I[3]*I[3] + I[4]*I[4] + I[5]*I[5] +
                                             I[6]*I[6] + I[7]*I[7] + I[8]*I[8]);
              if (weight>0) dest(x,y,z,v)/=(ftype)std::sqrt(weight);
            }
          } break;
          case 2: {
            T I[4] = { 0 };
            cimg_forZV(*this,z,v) cimg_for2x2(*this,x,y,z,v,I) dest(x,y,z,v) = (ftype)
              (I[0]*mask[0] + I[1]*mask[1] +
               I[2]*mask[2] + I[3]*mask[3]);
            if (weighted_correl) cimg_forZV(*this,z,v) cimg_for2x2(*this,x,y,z,v,I) {
              const double weight = (double)(I[0]*I[0] + I[1]*I[1] +
                                             I[2]*I[2] + I[3]*I[3]);
              if (weight>0) dest(x,y,z,v)/=(ftype)std::sqrt(weight);
            }
          } break;
          case 1: (dest.assign(*this))*=mask(0); break;
          }
        }
      } else { // Generic version for other masks
        const int
          mx2 = mask.dimx()/2, my2 = mask.dimy()/2, mz2 = mask.dimz()/2,
          mx1 = mx2 - 1 + (mask.dimx()%2), my1 = my2 - 1 + (mask.dimy()%2), mz1 = mz2 - 1 + (mask.dimz()%2),
          mxe = dimx() - mx2, mye = dimy() - my2, mze = dimz() - mz2;
        cimg_forV(*this,v)
          if (!weighted_correl) { // Classical correlation
            for (int z = mz1; z<mze; ++z) for (int y = my1; y<mye; ++y) for (int x = mx1; x<mxe; ++x) {
              ftype val = 0;
              for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm)
                val+=(*this)(x+xm,y+ym,z+zm,v)*mask(mx1+xm,my1+ym,mz1+zm);
              dest(x,y,z,v) = (ftype)val;
            }
            if (cond)
              cimg_forYZV(*this,y,z,v)
                for (int x = 0; x<dimx(); (y<my1 || y>=mye || z<mz1 || z>=mze)?++x:((x<mx1-1 || x>=mxe)?++x:(x=mxe))) {
                  ftype val = 0;
                  for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm)
                    val+=pix3d(x+xm,y+ym,z+zm,v)*mask(mx1+xm,my1+ym,mz1+zm);
                  dest(x,y,z,v) = (ftype)val;
                }
            else
              cimg_forYZV(*this,y,z,v)
                for (int x = 0; x<dimx(); (y<my1 || y>=mye || z<mz1 || z>=mze)?++x:((x<mx1-1 || x>=mxe)?++x:(x=mxe))) {
                  ftype val = 0;
                  for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm)
                    val+=pix3d(x+xm,y+ym,z+zm,v,0)*mask(mx1+xm,my1+ym,mz1+zm);
                  dest(x,y,z,v) = (ftype)val;
                }
          } else { // Weighted correlation
            for (int z = mz1; z<mze; ++z) for (int y = my1; y<mye; ++y) for (int x = mx1; x<mxe; ++x) {
              ftype val = 0, weight = 0;
              for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm) {
                const ftype cval = (ftype)(*this)(x+xm,y+ym,z+zm,v);
                val+=cval*mask(mx1+xm,my1+ym,mz1+zm);
                weight+=cval*cval;
              }
              dest(x,y,z,v) = (weight>0)?(ftype)(val/std::sqrt((double)weight)):0;
            }
            if (cond)
              cimg_forYZV(*this,y,z,v)
                for (int x = 0; x<dimx(); (y<my1 || y>=mye || z<mz1 || z>=mze)?++x:((x<mx1-1 || x>=mxe)?++x:(x=mxe))) {
                  ftype val = 0, weight = 0;
                  for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm) {
                    const ftype cval = (ftype)pix3d(x+xm,y+ym,z+zm,v);
                    val+=cval*mask(mx1+xm,my1+ym,mz1+zm);
                    weight+=cval*cval;
                  }
                  dest(x,y,z,v) = (weight>0)?(ftype)(val/std::sqrt((double)weight)):0;
                }
            else
              cimg_forYZV(*this,y,z,v)
                for (int x = 0; x<dimx(); (y<my1 || y>=mye || z<mz1 || z>=mze)?++x:((x<mx1-1 || x>=mxe)?++x:(x=mxe))) {
                  ftype val = 0, weight = 0;
                  for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm) {
                    const ftype cval = (ftype)pix3d(x+xm,y+ym,z+zm,v,0);
                    val+=cval*mask(mx1+xm,my1+ym,mz1+zm);
                    weight+=cval*cval;
                  }
                  dest(x,y,z,v) = (weight>0)?(ftype)(val/std::sqrt((double)weight)):0;
                }
          }
      }
      return dest;
    }

    //! In-place version of the previous function.
    template<typename t> CImg<T>& correlate(const CImg<t>& mask, const unsigned int cond=1, const bool weighted_correl=false) {
      return get_correlate(mask,cond,weighted_correl).transfer_to(*this);
    }

    //! Return the convolution of the image by a mask
    /**
       The result \p res of the convolution of an image \p img by a mask \p mask is defined to be :

       res(x,y,z) = sum_{i,j,k} img(x-i,y-j,z-k)*mask(i,j,k)

       \param mask = the correlation kernel.
       \param cond = the border condition type (0=zero, 1=dirichlet)
       \param weighted_convol = enable local normalization.
    **/
    template<typename t> CImg<typename cimg::superset2<T,t,float>::type>
      get_convolve(const CImg<t>& mask, const unsigned int cond=1, const bool weighted_convol=false) const {
      typedef typename cimg::superset2<T,t,float>::type ftype;
      if (is_empty()) return CImg<ftype>();
      if (!mask || mask.dim!=1)
        throw CImgArgumentException("CImg<%s>::convolve() : Specified mask (%u,%u,%u,%u,%p) is not scalar.",
                                    pixel_type(),mask.width,mask.height,mask.depth,mask.dim,mask.data);
      return get_correlate(CImg<t>(mask.ptr(),mask.size(),1,1,1,true).get_mirror('x').resize(mask,-1),cond,weighted_convol);
    }

    //! In-place version of the previous function.
    template<typename t> CImg<T>& convolve(const CImg<t>& mask, const unsigned int cond=1, const bool weighted_convol=false) {
      return get_convolve(mask,cond,weighted_convol).transfer_to(*this);
    }

    //! Return the erosion of the image by a structuring element.
    template<typename t> CImg<typename cimg::superset<T,t>::type>
    get_erode(const CImg<t>& mask, const unsigned int cond=1, const bool weighted_erosion=false) const {
      typedef typename cimg::superset<T,t>::type restype;
      if (is_empty()) return CImg<restype>();
      if (!mask || mask.dim!=1)
        throw CImgArgumentException("CImg<%s>::erode() : Specified mask (%u,%u,%u,%u,%p) is not a scalar image.",
                                    pixel_type(),mask.width,mask.height,mask.depth,mask.dim,mask.data);
      CImg<restype> dest(width,height,depth,dim);
      const int
        mx2 = mask.dimx()/2, my2 = mask.dimy()/2, mz2 = mask.dimz()/2,
        mx1 = mx2 - 1 + (mask.dimx()%2), my1 = my2 - 1 + (mask.dimy()%2), mz1 = mz2 - 1 + (mask.dimz()%2),
        mxe = dimx() - mx2, mye = dimy() - my2, mze = dimz() - mz2;
      cimg_forV(*this,v)
        if (!weighted_erosion) { // Classical erosion
          for (int z = mz1; z<mze; ++z) for (int y = my1; y<mye; ++y) for (int x = mx1; x<mxe; ++x) {
            restype min_val = cimg::type<restype>::max();
            for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm) {
              const restype cval = (restype)(*this)(x+xm,y+ym,z+zm,v);
              if (mask(mx1+xm,my1+ym,mz1+zm) && cval<min_val) min_val = cval;
            }
            dest(x,y,z,v) = min_val;
          }
          if (cond)
            cimg_forYZV(*this,y,z,v)
              for (int x = 0; x<dimx(); (y<my1 || y>=mye || z<mz1 || z>=mze)?++x:((x<mx1-1 || x>=mxe)?++x:(x=mxe))) {
                restype min_val = cimg::type<restype>::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 = (restype)pix3d(x+xm,y+ym,z+zm,v);
                  if (mask(mx1+xm,my1+ym,mz1+zm) && cval<min_val) min_val = cval;
                }
                dest(x,y,z,v) = min_val;
              }
          else
            cimg_forYZV(*this,y,z,v)
              for (int x = 0; x<dimx(); (y<my1 || y>=mye || z<mz1 || z>=mze)?++x:((x<mx1-1 || x>=mxe)?++x:(x=mxe))) {
                restype min_val = cimg::type<restype>::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 = (restype)pix3d(x+xm,y+ym,z+zm,v,0);
                  if (mask(mx1+xm,my1+ym,mz1+zm) && cval<min_val) min_val = cval;
                }
                dest(x,y,z,v) = min_val;
              }
        } else { // Weighted erosion
          for (int z = mz1; z<mze; ++z) for (int y = my1; y<mye; ++y) for (int x = mx1; x<mxe; ++x) {
            restype min_val = cimg::type<restype>::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 restype cval = (restype)((*this)(x+xm,y+ym,z+zm,v) + mval);
              if (mval && cval<min_val) min_val = cval;
            }
            dest(x,y,z,v) = min_val;
          }
          if (cond)
            cimg_forYZV(*this,y,z,v)
              for (int x = 0; x<dimx(); (y<my1 || y>=mye || z<mz1 || z>=mze)?++x:((x<mx1-1 || x>=mxe)?++x:(x=mxe))) {
                restype min_val = cimg::type<restype>::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 restype cval = (restype)(pix3d(x+xm,y+ym,z+zm,v) + mval);
                  if (mval && cval<min_val) min_val = cval;
                }
                dest(x,y,z,v) = min_val;
              }
          else
            cimg_forYZV(*this,y,z,v)
              for (int x = 0; x<dimx(); (y<my1 || y>=mye || z<mz1 || z>=mze)?++x:((x<mx1-1 || x>=mxe)?++x:(x=mxe))) {
                restype min_val = cimg::type<restype>::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 restype cval = (restype)(pix3d(x+xm,y+ym,z+zm,v,0) + mval);
                  if (mval && cval<min_val) min_val = cval;
                }
                dest(x,y,z,v) = min_val;
              }
        }
      return dest;
    }

    //! In-place version of the previous function.
    template<typename t> CImg<T>& erode(const CImg<t>& mask, const unsigned int cond=1, const bool weighted_erosion=false) {
      return get_erode(mask,cond,weighted_erosion).transfer_to(*this);
    }

    //! Erode the image by a square structuring element of size n
    CImg<T> get_erode(const unsigned int n, const unsigned int cond=1) const {
      static CImg<T> mask;
      if (n<2) return *this;
      if (mask.width!=n) mask.assign(n,n,1,1,1);
      const CImg<T> res = get_erode(mask,cond,false);
      if (n>20) mask.assign();
      return res;
    }

    //! In-place version of the previous function.
    CImg<T>& erode(const unsigned int n, const unsigned int cond=1) {
      if (n<2) return *this;
      return get_erode(n,cond).transfer_to(*this);
    }

    //! Return the dilatation of the image by a structuring element.
    template<typename t> CImg<typename cimg::superset<T,t>::type>
    get_dilate(const CImg<t>& mask, const unsigned int cond=1, const bool weighted_dilatation=false) const {
      typedef typename cimg::superset<T,t>::type restype;
      if (is_empty()) return CImg<restype>();
      if (!mask || mask.dim!=1)
        throw CImgArgumentException("CImg<%s>::dilate() : Specified mask (%u,%u,%u,%u,%p) is not a scalar image.",
                                    pixel_type(),mask.width,mask.height,mask.depth,mask.dim,mask.data);
      CImg<restype> dest(width,height,depth,dim);
      const int
        mx2 = mask.dimx()/2, my2 = mask.dimy()/2, mz2 = mask.dimz()/2,
        mx1 = mx2 - 1 + (mask.dimx()%2), my1 = my2 - 1 + (mask.dimy()%2), mz1 = mz2 - 1 + (mask.dimz()%2),
        mxe = dimx() - mx2, mye = dimy() - my2, mze = dimz() - mz2;
      cimg_forV(*this,v)
        if (!weighted_dilatation) { // Classical dilatation
          for (int z = mz1; z<mze; ++z) for (int y = my1; y<mye; ++y) for (int x = mx1; x<mxe; ++x) {
            restype max_val = cimg::type<restype>::min();
            for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm) {
              const restype cval = (restype)(*this)(x+xm,y+ym,z+zm,v);
              if (mask(mx1+xm,my1+ym,mz1+zm) && cval>max_val) max_val = cval;
            }
            dest(x,y,z,v) = max_val;
          }
          if (cond)
            cimg_forYZV(*this,y,z,v)
              for (int x = 0; x<dimx(); (y<my1 || y>=mye || z<mz1 || z>=mze)?++x:((x<mx1-1 || x>=mxe)?++x:(x=mxe))) {
                restype max_val = cimg::type<restype>::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 = (restype)pix3d(x+xm,y+ym,z+zm,v);
                  if (mask(mx1+xm,my1+ym,mz1+zm) && cval>max_val) max_val = cval;
                }
                dest(x,y,z,v) = max_val;
              }
          else
            cimg_forYZV(*this,y,z,v)
              for (int x = 0; x<dimx(); (y<my1 || y>=mye || z<mz1 || z>=mze)?++x:((x<mx1-1 || x>=mxe)?++x:(x=mxe))) {
                restype max_val = cimg::type<restype>::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 = (restype)pix3d(x+xm,y+ym,z+zm,v,0);
                  if (mask(mx1+xm,my1+ym,mz1+zm) && cval>max_val) max_val = cval;
                }
                dest(x,y,z,v) = max_val;
              }
        } else { // Weighted dilatation
          for (int z = mz1; z<mze; ++z) for (int y = my1; y<mye; ++y) for (int x = mx1; x<mxe; ++x) {
            restype max_val = cimg::type<restype>::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 restype cval = (restype)((*this)(x+xm,y+ym,z+zm,v) - mval);
              if (mval && cval>max_val) max_val = cval;
            }
            dest(x,y,z,v) = max_val;
          }
          if (cond)
            cimg_forYZV(*this,y,z,v)
              for (int x = 0; x<dimx(); (y<my1 || y>=mye || z<mz1 || z>=mze)?++x:((x<mx1-1 || x>=mxe)?++x:(x=mxe))) {
                restype max_val = cimg::type<restype>::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 restype cval = (restype)(pix3d(x+xm,y+ym,z+zm,v) - mval);
                  if (mval && cval>max_val) max_val = cval;
                }
                dest(x,y,z,v) = max_val;
              }
          else
            cimg_forYZV(*this,y,z,v)
              for (int x = 0; x<dimx(); (y<my1 || y>=mye || z<mz1 || z>=mze)?++x:((x<mx1-1 || x>=mxe)?++x:(x=mxe))) {
                restype max_val = cimg::type<restype>::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 restype cval = (restype)(pix3d(x+xm,y+ym,z+zm,v,0) - mval);
                  if (mval && cval>max_val) max_val = cval;
                }
                dest(x,y,z,v) = max_val;
              }
        }
      return dest;
    }

    template<typename t> CImg<T>& dilate(const CImg<t>& mask, const unsigned int cond=1, const bool weighted_dilatation=false) {
      return get_dilate(mask,cond,weighted_dilatation).transfer_to(*this);
    }

    //! Dilate the image by a square structuring element of size n
    CImg<T> get_dilate(const unsigned int n, const unsigned int cond=1) const {
      static CImg<T> mask;
      if (n<2) return *this;
      if (mask.width!=n) mask.assign(n,n,1,1,1);
      const CImg<T> res = get_dilate(mask,cond,false);
      if (n>20) mask.assign();
      return res;
    }

    //! In-place version of the previous function.
    CImg<T>& dilate(const unsigned int n, const unsigned int cond=1) {
      if (n<2) return *this;
      return get_dilate(n,cond).transfer_to(*this);
    }

    //! Add noise to the image.
    /**
       \param sigma = power of the noise. if sigma<0, it corresponds to the percentage of the maximum image value.
       \param ntype = noise type. can be 0=gaussian, 1=uniform or 2=Salt and Pepper.
       \return A noisy version of the instance image.
    **/
    CImg<T> get_noise(const double sigma=-20, const unsigned int ntype=0) const {
      return (+*this).noise(sigma,ntype);
    }

    //! In-place version of the previous function.
    CImg<T>& noise(const double sigma=-20, const unsigned int ntype=0) {
      if (!is_empty()) {
        double nsigma = sigma, max = (double)cimg::type<T>::max(), min = (double)cimg::type<T>::min();
        typedef typename cimg::superset<T,float>::type ftype;
        cimg::srand();
        ftype m = 0, M = 0;
        if (nsigma==0) return *this;
        if (nsigma<0 || ntype==2) m = (ftype)minmax(M);
        if (nsigma<0) nsigma = -nsigma*(M-m)/100.0;
        switch (ntype) {
        case 0: { // Gaussian noise
          cimg_for(*this,ptr,T) {
            double val = *ptr + nsigma*cimg::grand();
            if (val>max) val = max;
            if (val<min) val = min;
            *ptr = (T)val;
          }
        } break;
        case 1: { // Uniform noise
          cimg_for(*this,ptr,T) {
            double val = *ptr + nsigma*cimg::crand();
            if (val>max) val = max;
            if (val<min) val = min;
            *ptr = (T)val;
          }
        } break;
        case 2: { // Salt & Pepper noise
          if (M==m) { m = 0; M = 255; }
          cimg_for(*this,ptr,T) if (cimg::rand()*100<nsigma) *ptr = (T)(cimg::rand()<0.5?M:m);
        } break;
        case 3: { // Poisson Noise
          cimg_for(*this,ptr,T) {
            const double z = (double)*ptr;
            if (z<=1.0e-10) *ptr = (T)0;
            else {
              if (z>100.0) *ptr = (T)(unsigned int)((std::sqrt(z) * cimg::grand()) + z);
              else {
                unsigned int k = 0;
                const double y = std::exp(-z);
                for (double s = 1.0; s>=y; ++k) s *= cimg::rand();
                *ptr = (T)(k-1);
              }
            }
          }
        } break;
        case 4: { // Rice noise
          const double sqrt2 = (double)std::sqrt(2.0);
          cimg_for(*this,ptr,T) {
            const double
              val0 = (double)*ptr/sqrt2,
              re = val0 + nsigma*cimg::grand(),
              im = val0 + nsigma*cimg::grand();
            double val = std::sqrt(re*re + im*im);
            if (val>max) val = max;
            if (val<min) val = min;
            *ptr = (T)val;
          }
        } break;
        }
      }
      return *this;
    }

    //! Return 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.
       \see blur
    **/
    CImg<typename cimg::superset<T,float>::type> get_deriche(const float sigma, const int order=0,
                                                             const char axe='x', const bool cond=true) const {
      typedef typename cimg::superset<T,float>::type restype;
      return CImg<restype>(*this,false).deriche(sigma,order,axe,cond);
    }

    //! In-place version of the previous function.
    CImg<T>& deriche(const float sigma, const int order=0, const char axe='x', const bool cond=true) {
#define cimg_deriche2_apply \
  ftype *ptrY = Y.data, yb = 0, yp = 0; \
  T xp = (T)0; \
  if (cond) { xp = *ptrX; yb = yp = (ftype)(coefp*xp); } \
  for (int m=0; m<N; ++m) { \
    const T xc = *ptrX; ptrX+=off; \
    const ftype yc = *(ptrY++) = (ftype)(a0*xc + a1*xp - b1*yp - b2*yb); \
    xp = xc; yb = yp; yp = yc; \
  } \
  T xn = (T)0, xa = (T)0; \
  ftype yn = 0, ya = 0; \
  if (cond) { xn = xa = *(ptrX-off); yn = ya = (ftype)coefn*xn; } \
  for (int n=N-1; n>=0; --n) { \
    const T xc = *(ptrX-=off); \
    const ftype yc = (ftype)(a2*xn + a3*xa - b1*yn - b2*ya); \
    xa = xn; xn = xc; ya = yn; yn = yc; \
    *ptrX = (T)(*(--ptrY)+yc); \
  }
      typedef typename cimg::superset<T,float>::type ftype;
      if (sigma<0)
        throw CImgArgumentException("CImg<%s>::deriche() : Given filter variance (sigma = %g) is negative",pixel_type(),sigma);
      if (is_empty() || (sigma<0.1 && !order)) return *this;
      const float
        nsigma = sigma<0.1f?0.1f:sigma,
        alpha = 1.695f/nsigma,
        ema = (float)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)/ema;
        a0 = k*ema;
        a1 = a3 = 0;
        a2 = -a0;
      } 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<%s>::deriche() : Given filter order (order = %u) must be 0,1 or 2",pixel_type(),order);
        break;
      }
      coefp = (a0+a1)/(1+b1+b2);
      coefn = (a2+a3)/(1+b1+b2);
      switch (cimg::uncase(axe)) {
      case 'x': {
        const int N = width, off = 1;
        CImg<ftype> Y(N);
        cimg_forYZV(*this,y,z,v) { T *ptrX = ptr(0,y,z,v); cimg_deriche2_apply; }
      } break;
      case 'y': {
        const int N = height, off = width;
        CImg<ftype> Y(N);
        cimg_forXZV(*this,x,z,v) { T *ptrX = ptr(x,0,z,v); cimg_deriche2_apply; }
      } break;
      case 'z': {
        const int N = depth, off = width*height;
        CImg<ftype> Y(N);
        cimg_forXYV(*this,x,y,v) { T *ptrX = ptr(x,y,0,v); cimg_deriche2_apply; }
      } break;
      case 'v': {
        const int N = dim, off = width*height*depth;
        CImg<ftype> Y(N);
        cimg_forXYZ(*this,x,y,z) { T *ptrX = ptr(x,y,z,0); cimg_deriche2_apply; }
      } break;
      }
      return *this;
    }

    //! 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<typename cimg::superset<T,float>::type> get_blur(const float sigmax, const float sigmay, const float sigmaz,
                                                          const bool cond=true) const {
      typedef typename cimg::superset<T,float>::type restype;
      return CImg<restype>(*this,false).blur(sigmax,sigmay,sigmaz,cond);
    }

    //! In-place version of the previous function.
    CImg<T>& blur(const float sigmax, const float sigmay, const float sigmaz, const bool cond=true) {
      if (!is_empty()) {
        if (width>1  && sigmax>0) deriche(sigmax,0,'x',cond);
        if (height>1 && sigmay>0) deriche(sigmay,0,'y',cond);
        if (depth>1  && sigmaz>0) deriche(sigmaz,0,'z',cond);
      }
      return *this;
    }

    //! Return a blurred version of the image, using a Canny-Deriche filter.
     CImg<typename cimg::superset<T,float>::type> get_blur(const float sigma, const bool cond=true) const {
      typedef typename cimg::superset<T,float>::type restype;
      return CImg<restype>(*this,false).blur(sigma,cond);
    }

    //! In-place version of the previous function.
    CImg<T>& blur(const float sigma, const bool cond=true) {
      return blur(sigma,sigma,sigma,cond);
    }

    //! Get a blurred version of an image following a field of diffusion tensors.
    /**
       \param G = Field of square roots of diffusion tensors used to drive the smoothing.
       \param amplitude = amplitude of the smoothing.
       \param dl = spatial discretization.
       \param da = angular discretization.
       \param gauss_prec = precision of the gaussian function.
       \param interpolation Used interpolation scheme (0 = nearest-neighbor, 1 = linear, 2 = Runge-Kutta)
       \param fast_approx = Tell to use the fast approximation or not.
    **/
    template<typename t>
      CImg<T> get_blur_anisotropic(const CImg<t>& G, const float amplitude=60.0f, const float dl=0.8f, const float da=30.0f,
                                   const float gauss_prec=2.0f, const unsigned int interpolation=0, const bool fast_approx=true) const {
      return (+*this).blur_anisotropic(G,amplitude,dl,da,gauss_prec,interpolation,fast_approx);
    }

    //! In-place version of the previous function.
    template<typename t>
    CImg<T>& blur_anisotropic(const CImg<t>& G, const float amplitude=60.0f, const float dl=0.8f, const float da=30.0f,
                              const float gauss_prec=2.0f, const unsigned int interpolation=0, const bool fast_approx=true) {
#define cimg_valign2d(i,j) \
    { ftype &u = W(i,j,0,0), &v = W(i,j,0,1); \
    if (u*curru + v*currv<0) { u=-u; v=-v; }}
#define cimg_valign3d(i,j,k) \
    { ftype &u = W(i,j,k,0), &v = W(i,j,k,1), &w = W(i,j,k,2); \
    if (u*curru + v*currv + w*currw<0) { u=-u; v=-v; w=-w; }}

      // Check arguments and init variables
      typedef typename cimg::superset<T,float>::type ftype;
      if (!is_empty() && amplitude>0) {
        if (!G || (G.dim!=3 && G.dim!=6) || G.width!=width || G.height!=height || G.depth!=depth)
          throw CImgArgumentException("CImg<%s>::blur_anisotropic() : Specified tensor field (%u,%u,%u,%u) is not valid.",
                                      pixel_type(),G.width,G.height,G.depth,G.dim);

        const float sqrt2amplitude = (float)std::sqrt(2*amplitude);
        const bool threed = (G.dim>=6);
        const int
          dx1 = dimx()-1,
          dy1 = dimy()-1,
          dz1 = dimz()-1;
        CImg<ftype>
          dest(width,height,depth,dim,0),
          W(width,height,depth,threed?4:3),
          tmp(dim);
        int N = 0;

        if (threed)
          // 3D version of the algorithm
          for (float phi=(180%(int)da)/2.0f; phi<=180; phi+=da) {
            const float
              phir = (float)(phi*cimg::valuePI/180),
              datmp = (float)(da/std::cos(phir)),
              da2 = datmp<1?360.0f:datmp;

            for (float theta=0; theta<360; (theta+=da2),++N) {
              const float
                thetar = (float)(theta*cimg::valuePI/180),
                vx = (float)(std::cos(thetar)*std::cos(phir)),
                vy = (float)(std::sin(thetar)*std::cos(phir)),
                vz = (float)std::sin(phir);
              const t
                *pa = G.ptr(0,0,0,0),
                *pb = G.ptr(0,0,0,1),
                *pc = G.ptr(0,0,0,2),
                *pd = G.ptr(0,0,0,3),
                *pe = G.ptr(0,0,0,4),
                *pf = G.ptr(0,0,0,5);
              ftype
                *pd0 = W.ptr(0,0,0,0),
                *pd1 = W.ptr(0,0,0,1),
                *pd2 = W.ptr(0,0,0,2),
                *pd3 = W.ptr(0,0,0,3);
              cimg_forXYZ(G,xg,yg,zg) {
                const t
                  a = *(pa++), b = *(pb++), c = *(pc++),
                  d = *(pd++), e = *(pe++), f = *(pf++);
                const float
                  u = (float)(a*vx + b*vy + c*vz),
                  v = (float)(b*vx + d*vy + e*vz),
                  w = (float)(c*vx + e*vy + f*vz),
                  n = (float)std::sqrt(1e-5+u*u+v*v+w*w),
                  dln = dl/n;
                *(pd0++) = (ftype)(u*dln);
                *(pd1++) = (ftype)(v*dln);
                *(pd2++) = (ftype)(w*dln);
                *(pd3++) = (ftype)n;
              }

              cimg_forXYZ(*this,x,y,z) {
                tmp.fill(0);
                const float
                  cu = (float)W(x,y,z,0),
                  cv = (float)W(x,y,z,1),
                  cw = (float)W(x,y,z,2),
                  n  = (float)W(x,y,z,3),
                  fsigma = (float)(n*sqrt2amplitude),
                  length = gauss_prec*fsigma,
                  fsigma2 = 2*fsigma*fsigma;
                float
                  S = 0,
                  pu = cu,
                  pv = cv,
                  pw = cw,
                  X = (float)x,
                  Y = (float)y,
                  Z = (float)z;

                switch (interpolation) {
                case 0: {
                  // Nearest neighbor
                  for (float l=0; l<length && X>=0 && X<=dx1 && Y>=0 && Y<=dy1 && Z>=0 && Z<=dz1; l+=dl) {
                    const int
                      cx = (int)(X+0.5f),
                      cy = (int)(Y+0.5f),
                      cz = (int)(Z+0.5f);
                    float
                      u = (float)W(cx,cy,cz,0),
                      v = (float)W(cx,cy,cz,1),
                      w = (float)W(cx,cy,cz,2);
                    if ((pu*u + pv*v + pw*w)<0) { u=-u; v=-v; w=-w; }
                    if (fast_approx) { cimg_forV(*this,k) tmp[k]+=(ftype)(*this)(cx,cy,cz,k); ++S; }
                    else {
                      const float coef = (float)std::exp(-l*l/fsigma2);
                      cimg_forV(*this,k) tmp[k]+=(ftype)(coef*(*this)(cx,cy,cz,k));
                      S+=coef;
                    }
                    X+=(pu=u); Y+=(pv=v); Z+=(pw=w);
                  }
                } break;

                case 1: {
                  // Linear interpolation
                  for (float l=0; l<length && X>=0 && X<=dx1 && Y>=0 && Y<=dy1 && Z>=0 && Z<=dz1; l+=dl) {
                    const int
                      cx = (int)X, px = (cx-1<0)?0:cx-1, nx = (cx+1>dx1)?dx1:cx+1,
                      cy = (int)Y, py = (cy-1<0)?0:cy-1, ny = (cy+1>dy1)?dy1:cy+1,
                      cz = (int)Z, pz = (cz-1<0)?0:cz-1, nz = (cz+1>dz1)?dz1:cz+1;
                    const float
                      curru = (float)W(cx,cy,cz,0),
                      currv = (float)W(cx,cy,cz,1),
                      currw = (float)W(cx,cy,cz,2);
                    cimg_valign3d(px,py,pz); cimg_valign3d(cx,py,pz); cimg_valign3d(nx,py,pz);
                    cimg_valign3d(px,cy,pz); cimg_valign3d(cx,cy,pz); cimg_valign3d(nx,cy,pz);
                    cimg_valign3d(px,ny,pz); cimg_valign3d(cx,ny,pz); cimg_valign3d(nx,ny,pz);
                    cimg_valign3d(px,py,cz); cimg_valign3d(cx,py,cz); cimg_valign3d(nx,py,cz);
                    cimg_valign3d(px,cy,cz);                          cimg_valign3d(nx,cy,cz);
                    cimg_valign3d(px,ny,cz); cimg_valign3d(cx,ny,cz); cimg_valign3d(nx,ny,cz);
                    cimg_valign3d(px,py,nz); cimg_valign3d(cx,py,nz); cimg_valign3d(nx,py,nz);
                    cimg_valign3d(px,cy,nz); cimg_valign3d(cx,cy,nz); cimg_valign3d(nx,cy,nz);
                    cimg_valign3d(px,ny,nz); cimg_valign3d(cx,ny,nz); cimg_valign3d(nx,ny,nz);
                    float
                      u = (float)(W.linear_pix3d(X,Y,Z,0)),
                      v = (float)(W.linear_pix3d(X,Y,Z,1)),
                      w = (float)(W.linear_pix3d(X,Y,Z,2));
                    if ((pu*u + pv*v + pw*w)<0) { u=-u; v=-v; w=-w; }
                    if (fast_approx) { cimg_forV(*this,k) tmp[k]+=(ftype)linear_pix3d(X,Y,Z,k); ++S; }
                    else {
                      const float coef = (float)std::exp(-l*l/fsigma2);
                      cimg_forV(*this,k) tmp[k]+=(ftype)(coef*linear_pix3d(X,Y,Z,k));
                      S+=coef;
                    }
                    X+=(pu=u); Y+=(pv=v); Z+=(pw=w);
                  }
                } break;

                default: {
                  // 2nd order Runge Kutta
                  for (float l=0; l<length && X>=0 && X<=dx1 && Y>=0 && Y<=dy1 && Z>=0 && Z<=dz1; l+=dl) {
                    const int
                      cx = (int)X, px = (cx-1<0)?0:cx-1, nx = (cx+1>dx1)?dx1:cx+1,
                      cy = (int)Y, py = (cy-1<0)?0:cy-1, ny = (cy+1>dy1)?dy1:cy+1,
                      cz = (int)Z, pz = (cz-1<0)?0:cz-1, nz = (cz+1>dz1)?dz1:cz+1;
                    const float
                      curru = (float)W(cx,cy,cz,0),
                      currv = (float)W(cx,cy,cz,1),
                      currw = (float)W(cx,cy,cz,2);
                    cimg_valign3d(px,py,pz); cimg_valign3d(cx,py,pz); cimg_valign3d(nx,py,pz);
                    cimg_valign3d(px,cy,pz); cimg_valign3d(cx,cy,pz); cimg_valign3d(nx,cy,pz);
                    cimg_valign3d(px,ny,pz); cimg_valign3d(cx,ny,pz); cimg_valign3d(nx,ny,pz);
                    cimg_valign3d(px,py,cz); cimg_valign3d(cx,py,cz); cimg_valign3d(nx,py,cz);
                    cimg_valign3d(px,cy,cz);                          cimg_valign3d(nx,cy,cz);
                    cimg_valign3d(px,ny,cz); cimg_valign3d(cx,ny,cz); cimg_valign3d(nx,ny,cz);
                    cimg_valign3d(px,py,nz); cimg_valign3d(cx,py,nz); cimg_valign3d(nx,py,nz);
                    cimg_valign3d(px,cy,nz); cimg_valign3d(cx,cy,nz); cimg_valign3d(nx,cy,nz);
                    cimg_valign3d(px,ny,nz); cimg_valign3d(cx,ny,nz); cimg_valign3d(nx,ny,nz);
                    const float
                      u0 = (float)(0.5f*W.linear_pix3d(X,Y,Z,0)),
                      v0 = (float)(0.5f*W.linear_pix3d(X,Y,Z,1)),
                      w0 = (float)(0.5f*W.linear_pix3d(X,Y,Z,2));
                    float
                      u = (float)(W.linear_pix3d(X+u0,Y+v0,Z+w0,0)),
                      v = (float)(W.linear_pix3d(X+u0,Y+v0,Z+w0,1)),
                      w = (float)(W.linear_pix3d(X+u0,Y+v0,Z+w0,2));
                    if ((pu*u + pv*v + pw*w)<0) { u=-u; v=-v; w=-w; }
                    if (fast_approx) { cimg_forV(*this,k) tmp[k]+=(ftype)linear_pix3d(X,Y,Z,k); ++S; }
                    else {
                      const float coef = (float)std::exp(-l*l/fsigma2);
                      cimg_forV(*this,k) tmp[k]+=(ftype)(coef*linear_pix3d(X,Y,Z,k));
                      S+=coef;
                    }
                    X+=(pu=u); Y+=(pv=v); Z+=(pw=w);
                  }
                } break;
                }
                if (S>0) cimg_forV(dest,k) dest(x,y,z,k)+=tmp[k]/S;
                else cimg_forV(dest,k) dest(x,y,z,k)+=(ftype)((*this)(x,y,z,k));
                cimg_plugin_greycstoration_count;
              }
            }
          } else
            // 2D version of the algorithm
            for (float theta=(360%(int)da)/2.0f; theta<360; (theta+=da),++N) {
              const float
                thetar = (float)(theta*cimg::valuePI/180),
                vx = (float)(std::cos(thetar)),
                vy = (float)(std::sin(thetar));
              const t
                *pa = G.ptr(0,0,0,0),
                *pb = G.ptr(0,0,0,1),
                *pc = G.ptr(0,0,0,2);
              ftype
                *pd0 = W.ptr(0,0,0,0),
                *pd1 = W.ptr(0,0,0,1),
                *pd2 = W.ptr(0,0,0,2);
              cimg_forXY(G,xg,yg) {
                const t a = *(pa++), b = *(pb++), c = *(pc++);
                const float
                  u = (float)(a*vx + b*vy),
                  v = (float)(b*vx + c*vy),
                  n = (float)std::sqrt(1e-5+u*u+v*v),
                  dln = dl/n;
                *(pd0++) = (ftype)(u*dln);
                *(pd1++) = (ftype)(v*dln);
                *(pd2++) = (ftype)n;
              }

              cimg_forXY(*this,x,y) {
                tmp.fill(0);
                const float
                  cu = (float)W(x,y,0,0),
                  cv = (float)W(x,y,0,1),
                  n  = (float)W(x,y,0,2),
                  fsigma = (float)(n*sqrt2amplitude),
                  length = gauss_prec*fsigma,
                  fsigma2 = 2*fsigma*fsigma;
                float
                  S = 0,
                  pu = cu,
                  pv = cv,
                  X = (float)x,
                  Y = (float)y;

                switch (interpolation) {

                case 0: {
                  // Nearest-neighbor interpolation for 2D images
                  for (float l=0; l<length && X>=0 && X<=dx1 && Y>=0 && Y<=dy1; l+=dl) {
                    const int
                      cx = (int)(X+0.5f),
                      cy = (int)(Y+0.5f);
                    float
                      u = (float)W(cx,cy,0,0),
                      v = (float)W(cx,cy,0,1);
                    if ((pu*u + pv*v)<0) { u=-u; v=-v; }
                    if (fast_approx) { cimg_forV(*this,k) tmp[k]+=(ftype)(*this)(cx,cy,0,k); ++S; }
                    else {
                      const float coef = (float)std::exp(-l*l/fsigma2);
                      cimg_forV(*this,k) tmp[k]+=(ftype)(coef*(*this)(cx,cy,0,k));
                      S+=coef;
                    }
                    X+=(pu=u); Y+=(pv=v);
                  }
                } break;

                case 1: {
                  // Linear interpolation for 2D images
                  for (float l=0; l<length && X>=0 && X<=dx1 && Y>=0 && Y<=dy1; l+=dl) {
                    const int
                      cx = (int)X, px = (cx-1<0)?0:cx-1, nx = (cx+1>dx1)?dx1:cx+1,
                      cy = (int)Y, py = (cy-1<0)?0:cy-1, ny = (cy+1>dy1)?dy1:cy+1;
                    const float
                      curru = (float)W(cx,cy,0,0),
                      currv = (float)W(cx,cy,0,1);
                    cimg_valign2d(px,py); cimg_valign2d(cx,py); cimg_valign2d(nx,py);
                    cimg_valign2d(px,cy);                       cimg_valign2d(nx,cy);
                    cimg_valign2d(px,ny); cimg_valign2d(cx,ny); cimg_valign2d(nx,ny);
                    float
                      u = (float)(W.linear_pix2d(X,Y,0,0)),
                      v = (float)(W.linear_pix2d(X,Y,0,1));
                    if ((pu*u + pv*v)<0) { u=-u; v=-v; }
                    if (fast_approx) { cimg_forV(*this,k) tmp[k]+=(ftype)linear_pix2d(X,Y,0,k); ++S; }
                    else {
                      const float coef = (float)std::exp(-l*l/fsigma2);
                      cimg_forV(*this,k) tmp[k]+=(ftype)(coef*linear_pix2d(X,Y,0,k));
                      S+=coef;
                    }
                    X+=(pu=u); Y+=(pv=v);
                  }
                } break;

                default: {
                  // 2nd-order Runge-kutta interpolation for 2D images
                  for (float l=0; l<length && X>=0 && X<=dx1 && Y>=0 && Y<=dy1; l+=dl) {
                    const int
                      cx = (int)X, px = (cx-1<0)?0:cx-1, nx = (cx+1>dx1)?dx1:cx+1,
                      cy = (int)Y, py = (cy-1<0)?0:cy-1, ny = (cy+1>dy1)?dy1:cy+1;
                    const float
                      curru = (float)W(cx,cy,0,0),
                      currv = (float)W(cx,cy,0,1);
                    cimg_valign2d(px,py); cimg_valign2d(cx,py); cimg_valign2d(nx,py);
                    cimg_valign2d(px,cy);                       cimg_valign2d(nx,cy);
                    cimg_valign2d(px,ny); cimg_valign2d(cx,ny); cimg_valign2d(nx,ny);
                    const float
                      u0 = (float)(0.5f*W.linear_pix2d(X,Y,0,0)),
                      v0 = (float)(0.5f*W.linear_pix2d(X,Y,0,1));
                    float
                      u = (float)(W.linear_pix2d(X+u0,Y+v0,0,0)),
                      v = (float)(W.linear_pix2d(X+u0,Y+v0,0,1));
                    if ((pu*u + pv*v)<0) { u=-u; v=-v; }
                    if (fast_approx) { cimg_forV(*this,k) tmp[k]+=(ftype)linear_pix2d(X,Y,0,k); ++S; }
                    else {
                      const float coef = (float)std::exp(-l*l/fsigma2);
                      cimg_forV(*this,k) tmp[k]+=(ftype)(coef*linear_pix2d(X,Y,0,k));
                      S+=coef;
                    }
                    X+=(pu=u); Y+=(pv=v);
                  }
                } break;
                }
                if (S>0) cimg_forV(dest,k) dest(x,y,0,k)+=tmp[k]/S;
                else cimg_forV(dest,k) dest(x,y,0,k)+=(ftype)((*this)(x,y,0,k));
                cimg_plugin_greycstoration_count;
              }
            }
        const ftype *ptrs = dest.data+dest.size();
        const T m = cimg::type<T>::min(), M = cimg::type<T>::max();
        cimg_for(*this,ptrd,T) { const ftype val = *(--ptrs)/N; *ptrd = val<m?m:(val>M?M:(T)val); }
      }
      return *this;
    }

    //! Blur an image in an anisotropic way.
    /**
       \param mask Binary mask.
       \param amplitude Amplitude of the anisotropic blur.
       \param sharpness Contour preservation.
       \param anisotropy Smoothing anisotropy.
       \param alpha Image pre-blurring (gaussian).
       \param sigma Regularity of the tensor-valued geometry.
       \param dl Spatial discretization.
       \param da Angular discretization.
       \param gauss_prec Precision of the gaussian function.
       \param interpolation Used interpolation scheme (0 = nearest-neighbor, 1 = linear, 2 = Runge-Kutta)
       \param fast_approx Tell to use the fast approximation or not
       \param geom_factor Geometry factor.
    **/
    template<typename tm>
      CImg<T> get_blur_anisotropic(const CImg<tm>& mask, const float amplitude, const float sharpness=0.7f, const float anisotropy=0.3f,
                                   const float alpha=0.6f, const float sigma=1.1f, const float dl=0.8f,
                                   const float da=30.0f, const float gauss_prec=2.0f, const unsigned int interpolation=0,
                                   const bool fast_approx=true, const float geom_factor=1.0f) const {
      return (+*this).blur_anisotropic(mask,amplitude,sharpness,anisotropy,alpha,sigma,dl,da,gauss_prec,interpolation,fast_approx,geom_factor);
    }

    //! In-place version of the previous function.
    template<typename tm>
      CImg<T>& blur_anisotropic(const CImg<tm>& mask, const float amplitude, const float sharpness=0.7f, const float anisotropy=0.3f,
                                const float alpha=0.6f, const float sigma=1.1f, const float dl=0.8f, const float da=30.0f,
                                const float gauss_prec=2.0f, const unsigned int interpolation=0, const bool fast_approx=true,
                                const float geom_factor=1.0f) {
      if (!is_empty() && amplitude>0) {
        if (amplitude==0) return *this;
        if (amplitude<0 || sharpness<0 || anisotropy<0 || anisotropy>1 || alpha<0 || sigma<0 || dl<0 || da<0 || gauss_prec<0)
          throw CImgArgumentException("CImg<%s>::blur_anisotropic() : Given parameters are amplitude(%g), sharpness(%g), "
                                      "anisotropy(%g), alpha(%g), sigma(%g), dl(%g), da(%g), gauss_prec(%g).\n"
                                      "Admissible parameters are in the range : amplitude>0, sharpness>0, anisotropy in [0,1], "
                                      "alpha>0, sigma>0, dl>0, da>0, gauss_prec>0.",
                                      pixel_type(),amplitude,sharpness,anisotropy,alpha,sigma,dl,da,gauss_prec);
        const bool threed = (depth>1), no_mask = mask.is_empty();
        const float nsharpness = cimg::max(sharpness,1e-5f), power1 = 0.5f*nsharpness, power2 = power1/(1e-7f+1.0f-anisotropy);
        CImg<float> blurred = CImg<float>(*this,false).blur(alpha);
        if (geom_factor>0) blurred*=geom_factor;
        else blurred.normalize(0,-geom_factor);

        if (threed) { // Field for 3D volumes
          cimg_plugin_greycstoration_lock;
          CImg<float> val(3), vec(3,3), G(blurred.get_structure_tensorXYZ());
          if (sigma>0) G.blur(sigma);
          cimg_forXYZ(*this,x,y,z) {
            if (no_mask || mask(x,y,z)) {
              G.get_tensor_at(x,y,z).symmetric_eigen(val,vec);
              const float l1 = val[2], l2 = val[1], l3 = val[0],
                ux = vec(0,0), uy = vec(0,1), uz = vec(0,2),
                vx = vec(1,0), vy = vec(1,1), vz = vec(1,2),
                wx = vec(2,0), wy = vec(2,1), wz = vec(2,2),
                n1 = (float)std::pow(1.0f+l1+l2+l3,-power1),
                n2 = (float)std::pow(1.0f+l1+l2+l3,-power2);
              G(x,y,z,0) = n1*(ux*ux + vx*vx) + n2*wx*wx;
              G(x,y,z,1) = n1*(ux*uy + vx*vy) + n2*wx*wy;
              G(x,y,z,2) = n1*(ux*uz + vx*vz) + n2*wx*wz;
              G(x,y,z,3) = n1*(uy*uy + vy*vy) + n2*wy*wy;
              G(x,y,z,4) = n1*(uy*uz + vy*vz) + n2*wy*wz;
              G(x,y,z,5) = n1*(uz*uz + vz*vz) + n2*wz*wz;
            } else G(x,y,z,0) = G(x,y,z,1) = G(x,y,z,2) = G(x,y,z,3) = G(x,y,z,4) = G(x,y,z,5) = 0;
            cimg_plugin_greycstoration_count;
          }
          cimg_plugin_greycstoration_unlock;
          blur_anisotropic(G,amplitude,dl,da,gauss_prec,interpolation,fast_approx);
        } else { // Field for 2D images
          cimg_plugin_greycstoration_lock;
          CImg<float> val(2), vec(2,2), G(blurred.get_structure_tensorXY());
          if (sigma>0) G.blur(sigma);
          cimg_forXY(*this,x,y) {
            if (no_mask || mask(x,y)) {
              G.get_tensor_at(x,y).symmetric_eigen(val,vec);
              const float l1 = val[1], l2 = val[0],
                ux = vec(1,0), uy = vec(1,1),
                vx = vec(0,0), vy = vec(0,1),
                n1 = (float)std::pow(1.0f+l1+l2,-power1),
                n2 = (float)std::pow(1.0f+l1+l2,-power2);
              G(x,y,0,0) = n1*ux*ux + n2*vx*vx;
              G(x,y,0,1) = n1*ux*uy + n2*vx*vy;
              G(x,y,0,2) = n1*uy*uy + n2*vy*vy;
            } else G(x,y,0,0) = G(x,y,0,1) = G(x,y,0,2) = 0;
            cimg_plugin_greycstoration_count;
          }
          cimg_plugin_greycstoration_unlock;
          blur_anisotropic(G,amplitude,dl,da,gauss_prec,interpolation,fast_approx);
        }
      }
      return *this;
    }

    //! Blur an image following in an anisotropic way.
    CImg<T> get_blur_anisotropic(const float amplitude, const float sharpness=0.7f, const float anisotropy=0.3f,
                                 const float alpha=0.6f, const float sigma=1.1f, const float dl=0.8f,
                                 const float da=30.0f, const float gauss_prec=2.0f, const unsigned int interpolation=0,
                                 const bool fast_approx=true, const float geom_factor=1.0f) const {
      return (+*this).blur_anisotropic(amplitude,sharpness,anisotropy,alpha,sigma,dl,da,gauss_prec,interpolation,fast_approx,geom_factor);
    }

    //! In-place version of the previous function.
    CImg<T>& blur_anisotropic(const float amplitude, const float sharpness=0.7f, const float anisotropy=0.3f,
                              const float alpha=0.6f, const float sigma=1.1f, const float dl=0.8f, const float da=30.0f,
                              const float gauss_prec=2.0f, const unsigned int interpolation=0, const bool fast_approx=true,
                              const float geom_factor=1.0f) {
      return blur_anisotropic(CImg<T>(),amplitude,sharpness,anisotropy,alpha,sigma,dl,da,gauss_prec,interpolation,fast_approx,geom_factor);
    }

    //! Blur an image using the bilateral filter.
    /**
       \param sigmax Amount of blur along the X-axis.
       \param sigmay Amount of blur along the Y-axis.
       \param sigmaz Amount of blur along the Z-axis.
       \param sigmar Amount of blur along the range axis.
       \param bgridx Size of the bilateral grid along the X-axis.
       \param bgridy Size of the bilateral grid along the Y-axis.
       \param bgridz Size of the bilateral grid along the Z-axis.
       \param bgridr Size of the bilateral grid along the range axis.
       \param interpolation 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> get_blur_bilateral(const float sigmax, const float sigmay, const float sigmaz, const float sigmar,
                               const int bgridx, const int bgridy, const int bgridz, const int bgridr,
                               const bool interpolation=true) const {
      return (+*this).blur_bilateral(sigmax,sigmay,sigmaz,sigmar,bgridx,bgridy,bgridz,bgridr,interpolation);
    }

    //! In-place version of the previous function.
    CImg<T>& blur_bilateral(const float sigmax, const float sigmay, const float sigmaz, const float sigmar,
                            const int bgridx, const int bgridy, const int bgridz, const int bgridr,
                            const bool interpolation=true) {
      T m, M = maxmin(m);
      const float range = (float)(1.0f+M-m);
      const unsigned int
        bx0 = bgridx>=0?bgridx:width*(-bgridx)/100,
        by0 = bgridy>=0?bgridy:height*(-bgridy)/100,
        bz0 = bgridz>=0?bgridz:depth*(-bgridz)/100,
        br0 = bgridr>=0?bgridr:(int)(-range*bgridr/100),
        bx = bx0>0?bx0:1,
        by = by0>0?by0:1,
        bz = bz0>0?bz0:1,
        br = br0>0?br0:1;
      const float
        nsigmax = sigmax*bx/width,
        nsigmay = sigmay*by/height,
        nsigmaz = sigmaz*bz/depth,
        nsigmar = sigmar*br/range;
      if (nsigmax>0 || nsigmay>0 || nsigmaz>0 || nsigmar>0) {
        const bool threed = depth>1;
        if (threed) { // 3d version of the algorithm
          typedef typename cimg::last<T,float>::type ftype;
          CImg<ftype> bgrid(bx,by,bz,br), bgridw(bx,by,bz,br);
          cimg_forV(*this,k) {
            bgrid.fill(0); bgridw.fill(0);
            cimg_forXYZ(*this,x,y,z) {
              const T val = (*this)(x,y,z,k);
              const int X = x*bx/width, Y = y*by/height, Z = z*bz/depth, R = (int)((val-m)*br/range);
              bgrid(X,Y,Z,R) = (float)val;
              bgridw(X,Y,Z,R) = 1;
            }
            bgrid.blur(nsigmax,nsigmay,nsigmaz,true).deriche(nsigmar,0,'v',false);
            bgridw.blur(nsigmax,nsigmay,nsigmaz,true).deriche(nsigmar,0,'v',false);
            if (interpolation) cimg_forXYZ(*this,x,y,z) {
              const T val = (*this)(x,y,z,k);
              const float X = (float)x*bx/width, Y = (float)y*by/height, Z = (float)z*bz/depth, R = (val-m)*br/range,
                bval0 = bgrid.linear_pix4d(X,Y,Z,R), bval1 = bgridw.linear_pix4d(X,Y,Z,R);
              (*this)(x,y,z,k) = (T)(bval0/bval1);
            } else cimg_forXYZ(*this,x,y,z) {
              const T val = (*this)(x,y,z,k);
              const int X = x*bx/width, Y = y*by/height, Z = z*bz/depth, R = (int)((val-m)*br/range);
              const float bval0 = bgrid(X,Y,Z,R), bval1 = bgridw(X,Y,Z,R);
              (*this)(x,y,z,k) = (T)(bval0/bval1);
            }
          }
        } else { // 2d version of the algorithm
          typedef typename cimg::last<T,float>::type ftype;
          CImg<ftype> bgrid(bx,by,br,2);
          cimg_forV(*this,k) {
            bgrid.fill(0);
            cimg_forXY(*this,x,y) {
              const T val = (*this)(x,y,k);
              const int X = x*bx/width, Y = y*by/height, R = (int)((val-m)*br/range);
              bgrid(X,Y,R,0) = (float)val;
              bgrid(X,Y,R,1) = 1;
            }
            bgrid.blur(nsigmax,nsigmay,0,true).blur(0,0,nsigmar,false);
            if (interpolation) cimg_forXY(*this,x,y) {
              const T val = (*this)(x,y,k);
              const float X = (float)x*bx/width, Y = (float)y*by/height, R = (val-m)*br/range,
                bval0 = bgrid.linear_pix3d(X,Y,R,0), bval1 = bgrid.linear_pix3d(X,Y,R,1);
              (*this)(x,y,k) = (T)(bval0/bval1);
            } else cimg_forXY(*this,x,y) {
              const T val = (*this)(x,y,k);
              const int X = x*bx/width, Y = y*by/height, R = (int)((val-m)*br/range);
              const float bval0 = bgrid(X,Y,R,0), bval1 = bgrid(X,Y,R,1);
              (*this)(x,y,k) = (T)(bval0/bval1);
            }
          }
        }
      }
      return *this;
    }

    //! Blur an image using the bilateral filter.
    CImg<T> get_blur_bilateral(const float sigmas, const float sigmar, const int bgrids=-33, const int bgridr=32,
                               const bool interpolation=true) const {
      return (+*this).blur_bilateral(sigmas,sigmas,sigmas,sigmar,bgrids,bgrids,bgrids,bgridr,interpolation);
    }

    //! In-place version of the previous function.
    CImg<T>& blur_bilateral(const float sigmas, const float sigmar, const int bgrids=-33, const int bgridr=32,
                            const bool interpolation=true) {
      return blur_bilateral(sigmas,sigmas,sigmas,sigmar,bgrids,bgrids,bgrids,bgridr,interpolation);
    }

    //! Blur an image in its patch-based space.
    CImg<T> get_blur_patch(const unsigned int patch_size=3, const float sigma_p=10.0f, const float sigma_s=10.0f,
                           const unsigned int lookup_size=4, const bool fast_approx=true) const {
      return (+*this).blur_patch(patch_size,sigma_p,sigma_s,lookup_size,fast_approx);
    }

    //! Blur an image in its patch-based space.
    CImg<T>& blur_patch(const unsigned int patch_size=3, const float sigma_p=10.0f, const float sigma_s=10.0f,
                        const unsigned int lookup_size=4, const bool fast_approx=true) {

#define _cimg_blur_patch_fastfunc(x) ((x)>3?0:1)
#define _cimg_blur_patch_slowfunc(x) std::exp(-(x))
#define _cimg_blur_patch3d(N,func) { \
  const unsigned int N3 = N*N*N; \
  cimg_for##N##XYZ(*this,x,y,z) { \
    cimg_plugin_greycstoration_count; \
    cimg_forV(*this,k) cimg_get##N##x##N##x##N(*this,x,y,z,k,P.ptr(N3*k)); \
    const int x0 = x-rsize1, y0 = y-rsize1, z0 = z-rsize1, x1 = x+rsize2, y1 = y+rsize2, z1 = z+rsize2; \
    float sum_weights = 0; \
    cimg_for_in##N##XYZ(*this,x0,y0,z0,x1,y1,z1,p,q,r) { \
      cimg_forV(*this,k) cimg_get##N##x##N##x##N(*this,p,q,r,k,Q.ptr(N3*k)); \
      float distance2 = 0; \
      const T *pQ = Q.end(); \
      cimg_for(P,pP,T) { const float dI = (float)*pP-(float)*(--pQ); distance2+=dI*dI; } \
      distance2/=Pnorm; \
      const float dx = p-x, dy = q-y, dz = r-z, alldist = distance2 + (dx*dx+dy*dy+dz*dz)/sigma_s2, weight = func(alldist); \
      sum_weights+=weight; \
      { cimg_forV(*this,k) res(x,y,z,k)+=weight*(*this)(p,q,r,k); } \
    } \
    if (sum_weights>0) cimg_forV(*this,k) res(x,y,z,k)/=sum_weights; else cimg_forV(*this,k) res(x,y,z,k) = (*this)(x,y,z,k); \
  }}
#define _cimg_blur_patch2d(N,func) { \
  const unsigned int N2 = N*N; \
  cimg_for##N##XY(*this,x,y) { \
    cimg_plugin_greycstoration_count; \
    cimg_forV(*this,k) cimg_get##N##x##N(*this,x,y,0,k,P.ptr(N2*k)); \
    const int x0 = x-rsize1, y0 = y-rsize1, x1 = x+rsize2, y1 = y+rsize2; \
    float sum_weights = 0; \
    cimg_for_in##N##XY(*this,x0,y0,x1,y1,p,q) { \
      cimg_forV(*this,k) cimg_get##N##x##N(*this,p,q,0,k,Q.ptr(N2*k)); \
      float distance2 = 0; \
      const T *pQ = Q.end(); \
      cimg_for(P,pP,T) { const float dI = (float)*pP-(float)*(--pQ); distance2+=dI*dI; } \
      distance2/=Pnorm; \
      const float dx = p-x, dy = q-y, alldist = distance2 + (dx*dx+dy*dy)/sigma_s2, weight = func(alldist); \
      sum_weights+=weight; \
      { cimg_forV(*this,k) res(x,y,k)+=weight*(*this)(p,q,k); } \
    } \
    if (sum_weights>0) cimg_forV(*this,k) res(x,y,k)/=sum_weights; else cimg_forV(*this,k) res(x,y,k) = (*this)(x,y,k); \
  }}

      typedef typename cimg::superset<T,float>::type ftype;
      CImg<ftype> res(width,height,depth,dim,0);
      CImg<T> P(patch_size*patch_size*dim), Q(P);
      const float sigma_s2 = sigma_s*sigma_s, sigma_p2 = sigma_p*sigma_p, Pnorm = P.size()*sigma_p2;
      const int rsize2 = (int)lookup_size/2, rsize1 = rsize2-1+(lookup_size%2);
      if (depth>1) switch (patch_size) { // 3D version
      case 2:
        if (fast_approx) { _cimg_blur_patch3d(2,_cimg_blur_patch_fastfunc); }
        else { _cimg_blur_patch3d(2,_cimg_blur_patch_slowfunc); }
        break;
      case 3:
        if (fast_approx) { _cimg_blur_patch3d(3,_cimg_blur_patch_fastfunc); }
        else { _cimg_blur_patch3d(3,_cimg_blur_patch_slowfunc); }
        break;
      default: {
        const int psize1 = (int)patch_size/2, psize0 = psize1-1+(patch_size%2);
        cimg_forXYZ(*this,x,y,z) {
          cimg_plugin_greycstoration_count;
          P = get_crop(x-psize0,y-psize0,z-psize0,x+psize1,y+psize1,z+psize1,true);
          const int x0 = x-rsize1, y0 = y-rsize1, z0 = z-rsize1, x1 = x+rsize2, y1 = y+rsize2, z1 = z+rsize2;
          float sum_weights = 0;
          cimg_for_inXYZ(*this,x0,y0,z0,x1,y1,z1,p,q,r) {
            (Q = get_crop(p-psize0,q-psize0,r-psize0,p+psize1,q+psize1,r+psize1,true))-=P;
            const float
              dx = x-p, dy = y-q, dz = z-r,
              distance2 = Q.pow(2).sum()/Pnorm + (dx*dx + dy*dy + dz*dz)/sigma_s2,
              weight = std::exp(-distance2);
            sum_weights+=weight;
            cimg_forV(*this,k) res(x,y,z,k)+=weight*(*this)(p,q,r,k);
          }
          if (sum_weights>0) cimg_forV(*this,k) res(x,y,z,k)/=sum_weights; else cimg_forV(*this,k) res(x,y,z,k) = (*this)(x,y,z,k);
        }
      } break;
      } else switch (patch_size) { // 2D version
      case 2:
        if (fast_approx) { _cimg_blur_patch2d(2,_cimg_blur_patch_fastfunc); }
        else { _cimg_blur_patch2d(2,_cimg_blur_patch_slowfunc); }
        break;
      case 3:
        if (fast_approx) { _cimg_blur_patch2d(3,_cimg_blur_patch_fastfunc); }
        else { _cimg_blur_patch2d(3,_cimg_blur_patch_slowfunc); }
        break;
      case 4:
        if (fast_approx) { _cimg_blur_patch2d(4,_cimg_blur_patch_fastfunc); }
        else { _cimg_blur_patch2d(4,_cimg_blur_patch_slowfunc); }
        break;
      case 5:
        if (fast_approx) { _cimg_blur_patch2d(5,_cimg_blur_patch_fastfunc); }
        else { _cimg_blur_patch2d(5,_cimg_blur_patch_slowfunc); }
        break;
      case 6:
        if (fast_approx) { _cimg_blur_patch2d(6,_cimg_blur_patch_fastfunc); }
        else { _cimg_blur_patch2d(6,_cimg_blur_patch_slowfunc); }
        break;
      case 7:
        if (fast_approx) { _cimg_blur_patch2d(7,_cimg_blur_patch_fastfunc); }
        else { _cimg_blur_patch2d(7,_cimg_blur_patch_slowfunc); }
        break;
      case 8:
        if (fast_approx) { _cimg_blur_patch2d(8,_cimg_blur_patch_fastfunc); }
        else { _cimg_blur_patch2d(8,_cimg_blur_patch_slowfunc); }
        break;
      case 9:
        if (fast_approx) { _cimg_blur_patch2d(9,_cimg_blur_patch_fastfunc); }
        else { _cimg_blur_patch2d(9,_cimg_blur_patch_slowfunc); }
        break;
      default: {
        const int psize1 = (int)patch_size/2, psize0 = psize1-1+(patch_size%2);
        cimg_forXY(*this,x,y) {
          cimg_plugin_greycstoration_count;
          P = get_crop(x-psize0,y-psize0,x+psize1,y+psize1,true);
          const int x0 = x-rsize1, y0 = y-rsize1, x1 = x+rsize2, y1 = y+rsize2;
          float sum_weights = 0;
          cimg_for_inXY(*this,x0,y0,x1,y1,p,q) {
            (Q = get_crop(p-psize0,q-psize0,p+psize1,q+psize1,true))-=P;
            const float
              dx = x-p, dy = y-q,
              distance2 = Q.pow(2).sum()/Pnorm + (dx*dx + dy*dy)/sigma_s2,
              weight = std::exp(-distance2);
            sum_weights+=weight;
            cimg_forV(*this,k) res(x,y,0,k)+=weight*(*this)(p,q,0,k);
          }
          if (sum_weights>0) cimg_forV(*this,k) res(x,y,0,k)/=sum_weights; else cimg_forV(*this,k) res(x,y,0,k) = (*this)(x,y,0,k);
        }
      } break;
      }
      return res.transfer_to(*this);
    }

    //! Return the Fast Fourier Transform of an image (along a specified axis)
    CImgList<typename cimg::superset<T,float>::type> get_FFT(const char axe, const bool invert=false) const {
      typedef typename cimg::superset<T,float>::type restype;
      return CImgList<restype>(*this).FFT(axe,invert);
    }

    //! Return the Fast Fourier Transform on an image
    CImgList<typename cimg::superset<T,float>::type> get_FFT(const bool invert=false) const {
      typedef typename cimg::superset<T,float>::type restype;
      return CImgList<restype>(*this).FFT(invert);
    }

    //! Apply a median filter.
    CImg<T> get_blur_median(const unsigned int n=3) {
      CImg<T> res(width,height,depth,dim);
      if (!n || n==1) return *this;
      const int hl=n/2, hr=hl-1+n%2;
      if (res.depth!=1) {  // 3D median filter
        CImg<T> vois;
        cimg_forXYZV(*this,x,y,z,k) {
          const int
            x0 = x - hl, y0 = y - hl, z0 = z-hl, x1 = x + hr, y1 = y + hr, z1 = z+hr,
            nx0 = x0<0?0:x0, ny0 = y0<0?0:y0, nz0 = z0<0?0:z0,
            nx1 = x1>=dimx()?dimx()-1:x1, ny1 = y1>=dimy()?dimy()-1:y1, nz1 = z1>=dimz()?dimz()-1:z1;
          vois = get_crop(nx0,ny0,nz0,k,nx1,ny1,nz1,k);
          res(x,y,z,k) = vois.median();
        }
      } else {
#define _median_sort(a,b) if ((a)>(b)) cimg::swap(a,b)
        if (res.height!=1) switch (n) { // 2D median filter
        case 3: {
          T I[9] = { 0 };
          CImg_3x3(J,T);
          cimg_forV(*this,k) cimg_for3x3(*this,x,y,0,k,I) {
            std::memcpy(J,I,9*sizeof(T));
            _median_sort(Jcp, Jnp); _median_sort(Jcc, Jnc); _median_sort(Jcn, Jnn);
            _median_sort(Jpp, Jcp); _median_sort(Jpc, Jcc); _median_sort(Jpn, Jcn);
            _median_sort(Jcp, Jnp); _median_sort(Jcc, Jnc); _median_sort(Jcn, Jnn);
            _median_sort(Jpp, Jpc); _median_sort(Jnc, Jnn); _median_sort(Jcc, Jcn);
            _median_sort(Jpc, Jpn); _median_sort(Jcp, Jcc); _median_sort(Jnp, Jnc);
            _median_sort(Jcc, Jcn); _median_sort(Jcc, Jnp); _median_sort(Jpn, Jcc);
            _median_sort(Jcc, Jnp);
            res(x,y,0,k) = Jcc;
          }
        } break;
        case 5: {
          T I[25] = { 0 };
          CImg_5x5(J,T);
          cimg_forV(*this,k) cimg_for5x5(*this,x,y,0,k,I) {
            std::memcpy(J,I,25*sizeof(T));
            _median_sort(Jbb, Jpb); _median_sort(Jnb, Jab); _median_sort(Jcb, Jab); _median_sort(Jcb, Jnb);
            _median_sort(Jpp, Jcp); _median_sort(Jbp, Jcp); _median_sort(Jbp, Jpp); _median_sort(Jap, Jbc);
            _median_sort(Jnp, Jbc); _median_sort(Jnp, Jap); _median_sort(Jcc, Jnc); _median_sort(Jpc, Jnc);
            _median_sort(Jpc, Jcc); _median_sort(Jbn, Jpn); _median_sort(Jac, Jpn); _median_sort(Jac, Jbn);
            _median_sort(Jnn, Jan); _median_sort(Jcn, Jan); _median_sort(Jcn, Jnn); _median_sort(Jpa, Jca);
            _median_sort(Jba, Jca); _median_sort(Jba, Jpa); _median_sort(Jna, Jaa); _median_sort(Jcb, Jbp);
            _median_sort(Jnb, Jpp); _median_sort(Jbb, Jpp); _median_sort(Jbb, Jnb); _median_sort(Jab, Jcp);
            _median_sort(Jpb, Jcp); _median_sort(Jpb, Jab); _median_sort(Jpc, Jac); _median_sort(Jnp, Jac);
            _median_sort(Jnp, Jpc); _median_sort(Jcc, Jbn); _median_sort(Jap, Jbn); _median_sort(Jap, Jcc);
            _median_sort(Jnc, Jpn); _median_sort(Jbc, Jpn); _median_sort(Jbc, Jnc); _median_sort(Jba, Jna);
            _median_sort(Jcn, Jna); _median_sort(Jcn, Jba); _median_sort(Jpa, Jaa); _median_sort(Jnn, Jaa);
            _median_sort(Jnn, Jpa); _median_sort(Jan, Jca); _median_sort(Jnp, Jcn); _median_sort(Jap, Jnn);
            _median_sort(Jbb, Jnn); _median_sort(Jbb, Jap); _median_sort(Jbc, Jan); _median_sort(Jpb, Jan);
            _median_sort(Jpb, Jbc); _median_sort(Jpc, Jba); _median_sort(Jcb, Jba); _median_sort(Jcb, Jpc);
            _median_sort(Jcc, Jpa); _median_sort(Jnb, Jpa); _median_sort(Jnb, Jcc); _median_sort(Jnc, Jca);
            _median_sort(Jab, Jca); _median_sort(Jab, Jnc); _median_sort(Jac, Jna); _median_sort(Jbp, Jna);
            _median_sort(Jbp, Jac); _median_sort(Jbn, Jaa); _median_sort(Jpp, Jaa); _median_sort(Jpp, Jbn);
            _median_sort(Jcp, Jpn); _median_sort(Jcp, Jan); _median_sort(Jnc, Jpa); _median_sort(Jbn, Jna);
            _median_sort(Jcp, Jnc); _median_sort(Jcp, Jbn); _median_sort(Jpb, Jap); _median_sort(Jnb, Jpc);
            _median_sort(Jbp, Jcn); _median_sort(Jpc, Jcn); _median_sort(Jap, Jcn); _median_sort(Jab, Jbc);
            _median_sort(Jpp, Jcc); _median_sort(Jcp, Jac); _median_sort(Jab, Jpp); _median_sort(Jab, Jcp);
            _median_sort(Jcc, Jac); _median_sort(Jbc, Jac); _median_sort(Jpp, Jcp); _median_sort(Jbc, Jcc);
            _median_sort(Jpp, Jbc); _median_sort(Jpp, Jcn); _median_sort(Jcc, Jcn); _median_sort(Jcp, Jcn);
            _median_sort(Jcp, Jbc); _median_sort(Jcc, Jnn); _median_sort(Jcp, Jcc); _median_sort(Jbc, Jnn);
            _median_sort(Jcc, Jba); _median_sort(Jbc, Jba); _median_sort(Jbc, Jcc);
            res(x,y,0,k) = Jcc;
          }
        } break;
        default: {
          CImg<T> vois;
          cimg_forXYV(*this,x,y,k) {
            const int
              x0 = x - hl, y0 = y - hl, x1 = x + hr, y1 = y + hr,
              nx0 = x0<0?0:x0, ny0 = y0<0?0:y0,
              nx1 = x1>=dimx()?dimx()-1:x1, ny1 = y1>=dimy()?dimy()-1:y1;
            vois = get_crop(nx0,ny0,0,k,nx1,ny1,0,k);
            res(x,y,0,k) = vois.median();
          }
        } break;
        } else switch (n) { // 1D median filter
        case 2: {
          T I[4] = { 0 };
          cimg_forV(*this,k) cimg_for2x2(*this,x,y,0,k,I) res(x,0,0,k) = (T)(0.5f*(I[0]+I[1]));
        } break;
        case 3: {
          T I[9] = { 0 };
          cimg_forV(*this,k) cimg_for3x3(*this,x,y,0,k,I) {
            res(x,0,0,k) = I[3]<I[4]?
	      (I[4]<I[5]?I[4]:
	       (I[3]<I[5]?I[5]:I[3])):
	      (I[3]<I[5]?I[3]:
	       (I[4]<I[5]?I[5]:I[4]));
          }
        } break;
        default: {
          CImg<T> vois;
          cimg_forXV(*this,x,k) {
            const int
              x0 = x - hl, x1 = x + hr,
              nx0 = x0<0?0:x0, nx1 = x1>=dimx()?dimx()-1:x1;
            vois = get_crop(nx0,0,0,k,nx1,0,0,k);
            res(x,0,0,k) = vois.median();
          }
        } break;
        }
      }
      return res;
    }

    //! In-place version of the previous function.
    CImg<T>& blur_median(const unsigned int n=3) {
      return get_blur_median(n).transfer_to(*this);
    }

    //! Sharpen image using anisotropic shock filters
    CImg<T> get_sharpen(const float amplitude=50.0f, const float edge=1.0f, const float alpha=0.0f, const float sigma=0.0f) const {
      return (+*this).sharpen(amplitude,edge,alpha,sigma);
    }

    //! In-place version of the previous function.
    CImg<T>& sharpen(const float amplitude=50.0f, const float edge=1.0f, const float alpha=0.0f, const float sigma=0.0f) {
      if (is_empty()) return *this;
      const bool threed = (depth>1);
      const float nedge = 0.5f*edge;
      typedef typename cimg::superset<T,float>::type ftype;
      CImg<ftype> val, vec, veloc(width,height,depth,dim);

      if (threed) {
        CImg<ftype> G = (alpha>0?get_blur(alpha).get_structure_tensorXYZ():get_structure_tensorXYZ());
        if (sigma>0) G.blur(sigma);
        CImg_3x3x3(I,float);
        cimg_forXYZ(G,x,y,z) {
          G.get_tensor_at(x,y,z).symmetric_eigen(val,vec);
          G(x,y,z,0) = vec(0,0);
          G(x,y,z,1) = vec(0,1);
          G(x,y,z,2) = vec(0,2);
          G(x,y,z,3) = 1.0f-(float)std::pow((float)(1+val[0]+val[1]+val[2]),-nedge);
        }
        cimg_forV(*this,k) cimg_for3x3x3(*this,x,y,z,k,I) {
          const float
            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 = 0.25f*(Innc+Ippc-Inpc-Ipnc),
            ixz = 0.25f*(Incn+Ipcp-Incp-Ipcn),
            iyy = Icnc+Icpc-2*Iccc,
            iyz = 0.25f*(Icnn+Icpp-Icnp-Icpn),
            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(x,y,z,k) = -amp*cimg::sign(itt)*cimg::abs(it);
        }
      } else {
        CImg<ftype> G = (alpha>0?get_blur(alpha).get_structure_tensorXY():get_structure_tensorXY());
        if (sigma>0) G.blur(sigma);
        CImg_3x3(I,float);
        cimg_forXY(G,x,y) {
          G.get_tensor_at(x,y).symmetric_eigen(val,vec);
          G(x,y,0) = vec(0,0);
          G(x,y,1) = vec(0,1);
          G(x,y,2) = 1.0f-(float)std::pow((float)(1+val[0]+val[1]),-nedge);
        }
        cimg_forV(*this,k) cimg_for3x3(*this,x,y,0,k,I) {
          const float
            u = G(x,y,0),
            v = G(x,y,1),
            amp = G(x,y,2),
            ixx = Inc+Ipc-2*Icc,
            ixy = 0.25f*(Inn+Ipp-Inp-Ipn),
            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(x,y,k) = -amp*cimg::sign(itt)*cimg::abs(it);
        }
      }
      float m, M = (float)veloc.maxmin(m);
      const float vmax = (float)cimg::max(cimg::abs(m),cimg::abs(M));
      if (vmax!=0) { veloc*=amplitude/vmax; (*this)+=veloc; }
      return *this;
    }

    //! 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<typename cimg::superset<T,float>::type> get_haar(const char axis, const bool invert=false, const unsigned int nb_scales=1) const {
      if (is_empty() || !nb_scales) return *this;
      typedef typename cimg::superset<T,float>::type ftype;
      CImg<ftype> res;

      if (nb_scales==1) {
        switch (cimg::uncase(axis)) { // Single scale transform
        case 'x': {
          const unsigned int w = width/2;
          if (w) {
            if (w%2) throw CImgInstanceException("CImg<%s>::haar() : Sub-image width = %u is not even at a particular scale (=%u).",pixel_type(),w);
            res.assign(width,height,depth,dim);
            if (invert) cimg_forYZV(*this,y,z,v) { // Inverse transform along X
              for (unsigned int x=0, xw=w, x2=0; x<w; ++x, ++xw) {
                const ftype val0 = (ftype)(*this)(x,y,z,v), val1 = (ftype)(*this)(xw,y,z,v);
                res(x2++,y,z,v) = val0 - val1;
                res(x2++,y,z,v) = val0 + val1;
              }
            } else cimg_forYZV(*this,y,z,v) { // Direct transform along X
              for (unsigned int x=0, xw=w, x2=0; x<w; ++x, ++xw) {
                const ftype val0 = (ftype)(*this)(x2++,y,z,v), val1 = (ftype)(*this)(x2++,y,z,v);
                res(x,y,z,v) = (val0 + val1)/2;
                res(xw,y,z,v) = (val1 - val0)/2;
              }
            }
          } else return *this;
        } break;
        case 'y': {
          const unsigned int h = height/2;
          if (h) {
            if (h%2) throw CImgInstanceException("CImg<%s>::haar() : Sub-image height = %u is not even at a particular scale.",pixel_type(),h);
            res.assign(width,height,depth,dim);
            if (invert) cimg_forXZV(*this,x,z,v) { // Inverse transform along Y
              for (unsigned int y=0, yh=h, y2=0; y<h; ++y, ++yh) {
                const ftype val0 = (ftype)(*this)(x,y,z,v), val1 = (ftype)(*this)(x,yh,z,v);
                res(x,y2++,z,v) = val0 - val1;
                res(x,y2++,z,v) = val0 + val1;
              }
            } else cimg_forXZV(*this,x,z,v) {
              for (unsigned int y=0, yh=h, y2=0; y<h; ++y, ++yh) { // Direct transform along Y
                const ftype val0 = (ftype)(*this)(x,y2++,z,v), val1 = (ftype)(*this)(x,y2++,z,v);
                res(x,y,z,v) = (val0 + val1)/2;
                res(x,yh,z,v) = (val1 - val0)/2;
              }
            }
          } else return *this;
        } break;
        case 'z': {
          const unsigned int d = depth/2;
          if (d) {
            if (d%2) throw CImgInstanceException("CImg<%s>::haar() : Sub-image depth = %u is not even at a particular scale.",pixel_type(),d);
            res.assign(width,height,depth,dim);
            if (invert) cimg_forXYV(*this,x,y,v) { // Inverse transform along Z
              for (unsigned int z=0, zd=d, z2=0; z<d; ++z, ++zd) {
                const ftype val0 = (ftype)(*this)(x,y,z,v), val1 = (ftype)(*this)(x,y,zd,v);
                res(x,y,z2++,v) = val0 - val1;
                res(x,y,z2++,v) = val0 + val1;
              }
            } else cimg_forXYV(*this,x,y,v) {
              for (unsigned int z=0, zd=d, z2=0; z<d; ++z, ++zd) { // Direct transform along Z
                const ftype val0 = (ftype)(*this)(x,y,z2++,v), val1 = (ftype)(*this)(x,y,z2++,v);
                res(x,y,z,v) = (val0 + val1)/2;
                res(x,y,zd,v) = (val1 - val0)/2;
              }
            }
          } else return *this;
        } break;
        default: throw CImgArgumentException("CImg<%s>::haar() : Unknown axis '%c'.",pixel_type(),axis); break;
        }
      } 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),0);
          } 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),0);
          } 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),0);
          } break;
          default: throw CImgArgumentException("CImg<%s>::haar() : Unknown axis '%c'.",pixel_type(),axis); break;
          }
        } 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),0);
          } 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),0);
          } 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),0);
          } break;
          default: throw CImgArgumentException("CImg<%s>::haar() : Unknown axis '%c'.",pixel_type(),axis); break;
          }
        }
      }
      return res;
    }

    //! In-place version of the previous function.
    CImg<T>& haar(const char axis, const bool invert=false, const unsigned int nb_scales=1) {
      return get_haar(axis,invert,nb_scales).transfer_to(*this);
    }

    //! Compute the Haar multiscale wavelet transform.
    /**
       \param invert Set inverse of direct transform.
       \param nb_scales Number of scales used for the transform.
    **/
    CImg<typename cimg::superset<T,float>::type> get_haar(const bool invert=false, const unsigned int nb_scales=1) const {
      typedef typename cimg::superset<T,float>::type ftype;
      CImg<ftype> res;

      if (nb_scales==1) { // Single scale transform
        if (width>1) get_haar('x',invert,1).transfer_to(res);
        if (height>1) { if (res) res.get_haar('y',invert,1).transfer_to(res); else get_haar('y',invert,1).transfer_to(res); }
        if (depth>1) { if (res) res.get_haar('z',invert,1).transfer_to(res); else get_haar('z',invert,1).transfer_to(res); }
        if (res) return res;
      } else { // Multi-scale transform
        if (invert) { // Inverse transform
          res.assign(*this);
          if (width>1) {
            if (height>1) {
              if (depth>1) {
                unsigned int w = width, h = height, d = depth; for (unsigned int s=1; w && h && d && s<nb_scales; ++s) { w/=2; h/=2; d/=2; }
                for (w=w?w:1, h=h?h:1, d=d?d:1; w<=width && h<=height && d<=depth; w*=2, h*=2, d*=2)
                  res.draw_image(res.get_crop(0,0,0,w-1,h-1,d-1).get_haar(true,1),0,0,0);
              } 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),0,0,0);
              }
            } 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),0,0,0);
              } 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),0,0,0);
              }
            }
          } 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),0,0,0);
              } 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),0,0,0);
              }
            } 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),0,0,0);
              } 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),0,0,0);
              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),0,0,0);
            } 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),0,0,0);
              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),0,0,0);
            }
          } 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),0,0,0);
              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),0,0,0);
            } 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),0,0,0);
              else return *this;
            }
          }
        }
        return res;
      }
      return *this;
    }

    //! In-place version of the previous function.
    CImg<T>& haar(const bool invert=false, const unsigned int nb_scales=1) {
      return get_haar(invert,nb_scales).transfer_to(*this);
    }

    //! Estimate a displacement field between instance image and given target image.
    CImg<typename cimg::superset<T,float>::type>
      get_displacement_field(const CImg<T>& target,
                             const float smoothness=0.1f, const float precision=0.1f,
                             const unsigned int nb_scales=0, const unsigned int itermax=10000) const {
      typedef typename cimg::superset<T,float>::type ftype;
      if (is_empty() || !target) return *this;
      if (!is_sameXYZV(target))
        throw CImgArgumentException("CImg<%s>::displacement_field() : Instance image (%u,%u,%u,%u,%p) and target image (%u,%u,%u,%u,%p) "
                                    "have different size.",pixel_type(),width,height,depth,dim,data,
                                    target.width,target.height,target.depth,target.dim,target.data);
      if (smoothness<0)
        throw CImgArgumentException("CImg<%s>::displacement_field() : Smoothness parameter %g is negative.",pixel_type(),smoothness);
      if (precision<0)
        throw CImgArgumentException("CImg<%s>::displacement_field() : Precision parameter %g is negative.",pixel_type(),precision);

      const unsigned int nscales = nb_scales>0?nb_scales:(unsigned int)(2*std::log((double)(cimg::max(width,height,depth))));
      ftype m1, M1 = maxmin(m1), m2, M2 = target.maxmin(m2);
      const ftype factor = cimg::max(cimg::abs(m1),cimg::abs(M1),cimg::abs(m2),cimg::abs(M2));
      CImg<ftype> U0;
      const bool threed = (depth>1);

      // Begin multi-scale motion estimation
      for (int scale=(int)nscales-1; scale>=0; --scale) {
        const float sfactor = (float)std::pow(1.5f,(float)scale), sprecision = (float)(precision/std::pow(2.25,1+scale));
        const int
          sw = (int)(width/sfactor), sh = (int)(height/sfactor), sd = (int)(depth/sfactor),
          swidth = sw?sw:1, sheight = sh?sh:1, sdepth = sd?sd:1;
        CImg<ftype>
          I1 = get_resize(swidth,sheight,sdepth,-100,2),
          I2 = target.get_resize(swidth,sheight,sdepth,-100,2);
        I1/=factor; I2/=factor;
        CImg<ftype> U;
        if (U0) U = (U0*=1.5f).get_resize(I1.dimx(),I1.dimy(),I1.dimz(),-100,3);
        else U.assign(I1.dimx(),I1.dimy(),I1.dimz(),threed?3:2,0);

        // Begin single-scale motion estimation
        CImg<ftype> veloc(U);
        float dt = 2.0f, Energy = cimg::type<float>::max();
        const CImgList<ftype> dI = threed?I2.get_gradientXYZ():I2.get_gradientXY();
        for (unsigned int iter=0; iter<itermax; iter++) {
          veloc.fill(0);
          float nEnergy = 0;
          if (threed) {
            CImg_3x3x3(I,ftype);
            cimg_for3XYZ(U,x,y,z) {
              const float X = x + U(x,y,z,0), Y = y + U(x,y,z,1), Z = z + U(x,y,z,2);
              cimg_forV(U,k) {
                const float
                  Ux  = 0.5f*(U(_n1x,y,z,k) - U(_p1x,y,z,k)),
                  Uy  = 0.5f*(U(x,_n1y,z,k) - U(x,_p1y,z,k)),
                  Uz  = 0.5f*(U(x,y,_n1z,k) - U(x,y,_p1z,k)),
                  Uxx = U(_n1x,y,z,k) + U(_p1x,y,z,k) - 2*U(x,y,z,k),
                  Uyy = U(x,_n1y,z,k) + U(x,_p1y,z,k) - 2*U(x,y,z,k),
                  Uzz = U(x,y,_n1z,k) + U(x,y,_n1z,k) - 2*U(x,y,z,k);
                nEnergy += smoothness*(Ux*Ux + Uy*Uy + Uz*Uz);
                float deltaIgrad = 0;
                cimg_forV(I1,i) {
                  const float deltaIi = (float)(I2.linear_pix3d(X,Y,Z,i) - I1(x,y,z,i));
                  nEnergy += deltaIi*deltaIi/2;
                  deltaIgrad+=-deltaIi*dI[k].linear_pix3d(X,Y,Z,i);
                }
                veloc(x,y,z,k) = deltaIgrad + smoothness*(Uxx + Uyy + Uzz);
              }
            }
          } else {
            CImg_3x3(I,ftype);
            cimg_for3XY(U,x,y) {
              const float X = x + U(x,y,0), Y = y + U(x,y,1);
              cimg_forV(U,k) {
                const float
                  Ux  = 0.5f*(U(_n1x,y,k) - U(_p1x,y,k)),
                  Uy  = 0.5f*(U(x,_n1y,k) - U(x,_p1y,k)),
                  Uxx = U(_n1x,y,k) + U(_p1x,y,k) - 2*U(x,y,k),
                  Uyy = U(x,_n1y,k) + U(x,_p1y,k) - 2*U(x,y,k);
                nEnergy += smoothness*(Ux*Ux + Uy*Uy);
                float deltaIgrad = 0;
                cimg_forV(I1,i) {
                  const float deltaIi = (float)(I2.linear_pix2d(X,Y,i) - I1(x,y,i));
                  nEnergy += deltaIi*deltaIi/2;
                  deltaIgrad+=-deltaIi*dI[k].linear_pix2d(X,Y,i);
                }
                veloc(x,y,k) = deltaIgrad + smoothness*(Uxx + Uyy);
              }
            }
          }
          const float vmax = cimg::max(cimg::abs(veloc.min()), cimg::abs(veloc.max()));
          U+=(veloc*=dt/vmax);
          if (cimg::abs(nEnergy-Energy)<sprecision) break;
          if (nEnergy<Energy) dt*=0.5f;
          Energy = nEnergy;
        }
        U.transfer_to(U0);
      }
      return U0;
    }

    //! In-place version of the previous function.
    CImg<T>& displacement_field(const CImg<T>& target, const float smooth=0.1f, const float precision=0.1f,
                                const unsigned int nb_scales=0, const unsigned int itermax=10000) {
      return get_displacement_field(target,smooth,precision,nb_scales,itermax).transfer_to(*this);
    }

    //@}
    //-----------------------------
    //
    //! \name Matrix and Vectors
    //@{
    //-----------------------------

    //! Return a vector with specified coefficients
    static CImg<T> vector(const T a0) {
      static CImg<T> r(1,1); r[0] = a0;
      return r;
    }

    //! Return a vector with specified coefficients
    static CImg<T> vector(const T a0, const T a1) {
      static CImg<T> r(1,2); T *ptr = r.data;
      *(ptr++) = a0; *(ptr++) = a1;
      return r;
    }

    //! Return a vector with specified coefficients
    static CImg<T> vector(const T a0, const T a1, const T a2) {
      static CImg<T> r(1,3); T *ptr = r.data;
      *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2;
      return r;
    }

    //! Return a vector with specified coefficients
    static CImg<T> vector(const T a0, const T a1, const T a2, const T a3) {
      static CImg<T> r(1,4); T *ptr = r.data;
      *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3;
      return r;
    }

    //! Return a vector with specified coefficients
    static CImg<T> vector(const T a0, const T a1, const T a2, const T a3, const T a4) {
      static CImg<T> r(1,5); T *ptr = r.data;
      *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3; *(ptr++) = a4;
      return r;
    }

    //! Return a vector with specified coefficients
    static CImg<T> vector(const T a0, const T a1, const T a2, const T a3, const T a4, const T a5) {
      static CImg<T> r(1,6); T *ptr = r.data;
      *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3; *(ptr++) = a4; *(ptr++) = a5;
      return r;
    }

    //! Return a vector with specified coefficients
    static CImg<T> vector(const T a0, const T a1, const T a2, const T a3,
		       const T a4, const T a5, const T a6) {
      static CImg<T> r(1,7); T *ptr = r.data;
      *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3; *(ptr++) = a4; *(ptr++) = a5; *(ptr++) = a6;
      return r;
    }

    //! Return a vector with specified coefficients
    static CImg<T> vector(const T a0, const T a1, const T a2, const T a3,
                          const T a4, const T a5, const T a6, const T a7) {
      static CImg<T> r(1,8); T *ptr = r.data;
      *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3; *(ptr++) = a4; *(ptr++) = a5; *(ptr++) = a6; *(ptr++) = a7;
      return r;
    }

    //! Return a vector with specified coefficients
    static CImg<T> vector(const T a0, const T a1, const T a2, const T a3,
                          const T a4, const T a5, const T a6, const T a7, const T a8) {
      static CImg<T> r(1,9); T *ptr = r.data;
      *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3; *(ptr++) = a4; *(ptr++) = a5; *(ptr++) = a6; *(ptr++) = a7;
      *(ptr++) = a8;
      return r;
    }

    //! Return a vector with specified coefficients
    static CImg<T> vector(const T a0, const T a1, const T a2, const T a3,
                          const T a4, const T a5, const T a6, const T a7,
                          const T a8, const T a9) {
      static CImg<T> r(1,10); T *ptr = r.data;
      *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3; *(ptr++) = a4; *(ptr++) = a5; *(ptr++) = a6; *(ptr++) = a7;
      *(ptr++) = a8; *(ptr++) = a9;
      return r;
    }

    //! Return a vector with specified coefficients
    static CImg<T> vector(const T a0, const T a1, const T a2, const T a3,
                          const T a4, const T a5, const T a6, const T a7,
                          const T a8, const T a9, const T a10) {
      static CImg<T> r(1,11); T *ptr = r.data;
      *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3; *(ptr++) = a4; *(ptr++) = a5; *(ptr++) = a6; *(ptr++) = a7;
      *(ptr++) = a8; *(ptr++) = a9; *(ptr++) = a10;
      return r;
    }

    //! Return a vector with specified coefficients
    static CImg<T> vector(const T a0, const T a1, const T a2, const T a3,
                          const T a4, const T a5, const T a6, const T a7,
                          const T a8, const T a9, const T a10, const T a11) {
      static CImg<T> r(1,12); T *ptr = r.data;
      *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3; *(ptr++) = a4; *(ptr++) = a5; *(ptr++) = a6; *(ptr++) = a7;
      *(ptr++) = a8; *(ptr++) = a9; *(ptr++) = a10; *(ptr++) = a11;
      return r;
    }

    //! Return a vector with specified coefficients
    static CImg<T> vector(const T a0, const T a1, const T a2, const T a3,
                          const T a4, const T a5, const T a6, const T a7,
                          const T a8, const T a9, const T a10, const T a11,
                          const T a12) {
      static CImg<T> r(1,13); T *ptr = r.data;
      *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3; *(ptr++) = a4; *(ptr++) = a5; *(ptr++) = a6; *(ptr++) = a7;
      *(ptr++) = a8; *(ptr++) = a9; *(ptr++) = a10; *(ptr++) = a11; *(ptr++) = a12;
      return r;
    }

    //! Return a vector with specified coefficients
    static CImg<T> vector(const T a0, const T a1, const T a2, const T a3,
                          const T a4, const T a5, const T a6, const T a7,
                          const T a8, const T a9, const T a10, const T a11,
                          const T a12, const T a13) {
      static CImg<T> r(1,14); T *ptr = r.data;
      *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3; *(ptr++) = a4; *(ptr++) = a5; *(ptr++) = a6; *(ptr++) = a7;
      *(ptr++) = a8; *(ptr++) = a9; *(ptr++) = a10; *(ptr++) = a11; *(ptr++) = a12; *(ptr++) = a13;
      return r;
    }

    //! Return a vector with specified coefficients
    static CImg<T> vector(const T a0, const T a1, const T a2, const T a3,
                          const T a4, const T a5, const T a6, const T a7,
                          const T a8, const T a9, const T a10, const T a11,
                          const T a12, const T a13, const T a14) {
      static CImg<T> r(1,15); T *ptr = r.data;
      *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3; *(ptr++) = a4; *(ptr++) = a5; *(ptr++) = a6; *(ptr++) = a7;
      *(ptr++) = a8; *(ptr++) = a9; *(ptr++) = a10; *(ptr++) = a11; *(ptr++) = a12; *(ptr++) = a13; *(ptr++) = a14;
      return r;
    }

    //! Return a vector with specified coefficients
    static CImg<T> vector(const T a0, const T a1, const T a2, const T a3,
                          const T a4, const T a5, const T a6, const T a7,
                          const T a8, const T a9, const T a10, const T a11,
                          const T a12, const T a13, const T a14, const T a15) {
      static CImg<T> r(1,16); T *ptr = r.data;
      *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3; *(ptr++) = a4; *(ptr++) = a5; *(ptr++) = a6; *(ptr++) = a7;
      *(ptr++) = a8; *(ptr++) = a9; *(ptr++) = a10; *(ptr++) = a11; *(ptr++) = a12; *(ptr++) = a13; *(ptr++) = a14; *(ptr++) = a15;
      return r;
    }

    //! Return a vector with specified coefficients
    template<int N> static CImg<T> vector(const int a0, const int a1, ...) {
      CImg<T> res(1,N);
      _CImg_stdarg(res,a0,a1,N,int);
      return res;
    }

    //! Return a vector with specified coefficients
    template<int N> static CImg<T> vector(const double a0, const double a1, ...) {
      CImg<T> res(1,N);
      _CImg_stdarg(res,a0,a1,N,double);
      return res;
    }

    //! Return a 1x1 square matrix with specified coefficients
    static CImg<T> matrix(const T a0) {
      return vector(a0);
    }

    //! Return a 2x2 square matrix with specified coefficients
    static CImg<T> matrix(const T a0, const T a1,
                          const T a2, const T a3) {
      static CImg<T> r(2,2); T *ptr = r.data;
      *(ptr++) = a0; *(ptr++) = a1;
      *(ptr++) = a2; *(ptr++) = a3;
      return r;
    }

    //! Return a 3x3 square matrix with specified coefficients
    static CImg<T> matrix(const T a0, const T a1, const T a2,
                          const T a3, const T a4, const T a5,
                          const T a6, const T a7, const T a8) {
      static CImg<T> r(3,3); T *ptr = r.data;
      *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2;
      *(ptr++) = a3; *(ptr++) = a4; *(ptr++) = a5;
      *(ptr++) = a6; *(ptr++) = a7; *(ptr++) = a8;
      return r;
    }

    //! Return a 4x4 square matrix with specified coefficients
    static CImg<T> matrix(const T a0, const T a1, const T a2, const T a3,
                          const T a4, const T a5, const T a6, const T a7,
                          const T a8, const T a9, const T a10, const T a11,
                          const T a12, const T a13, const T a14, const T a15) {
      static CImg<T> r(4,4); T *ptr = r.data;
      *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3;
      *(ptr++) = a4; *(ptr++) = a5; *(ptr++) = a6; *(ptr++) = a7;
      *(ptr++) = a8; *(ptr++) = a9; *(ptr++) = a10; *(ptr++) = a11;
      *(ptr++) = a12; *(ptr++) = a13; *(ptr++) = a14; *(ptr++) = a15;
      return r;
    }

    //! Return a 5x5 square matrix with specified coefficients
    static CImg<T> matrix(const T a0, const T a1, const T a2, const T a3, const T a4,
                          const T a5, const T a6, const T a7, const T a8, const T a9,
                          const T a10, const T a11, const T a12, const T a13, const T a14,
                          const T a15, const T a16, const T a17, const T a18, const T a19,
                          const T a20, const T a21, const T a22, const T a23, const T a24) {
      static CImg<T> r(5,5); T *ptr = r.data;
      *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3; *(ptr++) = a4;
      *(ptr++) = a5; *(ptr++) = a6; *(ptr++) = a7; *(ptr++) = a8; *(ptr++) = a9;
      *(ptr++) = a10; *(ptr++) = a11; *(ptr++) = a12; *(ptr++) = a13; *(ptr++) = a14;
      *(ptr++) = a15; *(ptr++) = a16; *(ptr++) = a17; *(ptr++) = a18; *(ptr++) = a19;
      *(ptr++) = a20; *(ptr++) = a21; *(ptr++) = a22; *(ptr++) = a23; *(ptr++) = a24;
      return r;
    }

    //! Return a MxN square matrix with specified coefficients
    template<int M,int N> static CImg<T> matrix(const int a0, const int a1, ...) {
      CImg<T> res(M,N);
      _CImg_stdarg(res,a0,a1,M*N,int);
      return res;
    }

    //! Return a NxN square matrix with specified coefficients
    template<int M,int N> static CImg<T> matrix(const double a0, const double a1, ...) {
      CImg<T> res(M,N);
      _CImg_stdarg(res,a0,a1,M*N,double);
      return res;
    }

    //! 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 diagonal matrix with specified coefficients
    template<int N> static CImg<T> diagonal(const int a0, ...) {
      CImg<T> res;
      if (N>0) {
        res.assign(N,N,1,1,0);
        va_list ap;
        va_start(ap,a0);
        res[0] = (T)a0;
        for (int i=1; i<N; ++i) res(i,i) = (T)va_arg(ap,int);
        va_end(ap);
      }
      return res;
    }

    //! Return a NxN diagonal matrix with specified coefficients
    template<int N> static CImg<T> diagonal(const double a0, ...) {
      CImg<T> res;
      if (N>0) {
        res.assign(N,N,1,1,0);
        va_list ap;
        va_start(ap,a0);
        res[0] = (T)a0;
        for (int i=1; i<N; ++i) res(i,i) = (T)va_arg(ap,double);
        va_end(ap);
      }
      return res;
    }

    //! 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)));
    }

    //! 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=0, const unsigned int y=0, const unsigned int z=0) const {
      static CImg<T> dest;
      if (dest.height!=dim) dest.assign(1,dim);
      const unsigned int whz = width*height*depth;
      const T *ptrs = ptr(x,y,z);
      T *ptrd = dest.data;
      cimg_forV(*this,k) { *(ptrd++) = *ptrs; ptrs+=whz; }
      return dest;
    }

    //! Set the image \p vec as the \a vector \a valued pixel located at (\p x,\p y,\p z) of the current vector-valued image.
    template<typename t> CImg<T>& set_vector_at(const CImg<t>& vec, const unsigned int x=0, const unsigned int y=0, const unsigned int z=0) {
      if (x<width && y<height && z<depth) {
        const unsigned int whz = width*height*depth;
        const t *ptrs = vec.data;
        T *ptrd = ptr(x,y,z);
        for (unsigned int k=cimg::min((unsigned int)vec.size(),dim); k; --k) { *ptrd = (T)*(ptrs++); ptrd+=whz; }
      }
      return *this;
    }

    //! Return a new image corresponding to the \a square \a matrix located at (\p x,\p y,\p z) of the current vector-valued image.
    CImg<T> get_matrix_at(const unsigned int x=0, const unsigned int y=0, const unsigned int z=0) const {
      const int n = (int)std::sqrt((double)dim);
      CImg<T> dest(n,n);
      cimg_forV(*this,k) dest[k]=(*this)(x,y,z,k);
      return dest;
    }

    //! Set the image \p vec as the \a square \a matrix-valued pixel located at (\p x,\p y,\p z) of the current vector-valued image.
    template<typename t> CImg<T>& set_matrix_at(const CImg<t>& mat, const unsigned int x=0, const unsigned int y=0, const unsigned int z=0) {
      return set_vector_at(mat,x,y,z);
    }

    //! Return a new image corresponding to the \a diffusion \a tensor located at (\p x,\p y,\p z) of the current vector-valued image.
    CImg<T> get_tensor_at(const unsigned int x=0, const unsigned int y=0, const unsigned int z=0) const {
      if (dim==6) return tensor((*this)(x,y,z,0),(*this)(x,y,z,1),(*this)(x,y,z,2),
				(*this)(x,y,z,3),(*this)(x,y,z,4),(*this)(x,y,z,5));
      if (dim==3) return tensor((*this)(x,y,z,0),(*this)(x,y,z,1),(*this)(x,y,z,2));
      return tensor((*this)(x,y,z,0));
    }

    //! Set the image \p vec as the \a tensor \a valued pixel located at (\p x,\p y,\p z) of the current vector-valued image.
    template<typename t> CImg<T>& set_tensor_at(const CImg<t>& ten, const unsigned int x=0, const unsigned int y=0, const unsigned int z=0) {
      if (ten.height==2) {
        (*this)(x,y,z,0) = (T)ten[0];
        (*this)(x,y,z,1) = (T)ten[1];
        (*this)(x,y,z,2) = (T)ten[3];
      }
      else {
        (*this)(x,y,z,0) = (T)ten[0];
        (*this)(x,y,z,1) = (T)ten[1];
        (*this)(x,y,z,2) = (T)ten[2];
        (*this)(x,y,z,3) = (T)ten[4];
        (*this)(x,y,z,4) = (T)ten[5];
        (*this)(x,y,z,5) = (T)ten[8];
      }
      return *this;
    }

    //! Unroll all images values into a one-column vector.
    CImg<T> get_vector() const {
      return get_unroll('y');
    }

    //! In-place version of the previous function.
    CImg<T>& vector() {
      return unroll('y');
    }

    //! Realign pixel values of the instance image as a square matrix
    CImg<T> get_matrix() const {
      return (+*this).matrix();
    }

    //! In-place version of the previous function.
    CImg<T>& matrix() {
      const unsigned int siz = size();
      switch (siz) {
      case 1: break;
      case 4: width = height = 2; break;
      case 9: width = height = 3; break;
      case 16: width = height = 4; break;
      case 25: width = height = 5; break;
      case 36: width = height = 6; break;
      case 49: width = height = 7; break;
      case 64: width = height = 8; break;
      case 81: width = height = 9; break;
      case 100: width = height = 10; break;
      default: {
        unsigned int i = 11, i2 = i*i;
        while (i2<siz) { i2+=2*i+1; ++i; }
        if (i2==siz) width = height = i;
        else throw CImgInstanceException("CImg<%s>::matrix() : Image size = %u is not a square number",pixel_type(),siz);
      } break;
      }
      return *this;
    }

    //! Realign pixel values of the instance image as a symmetric tensor.
    CImg<T> get_tensor() const {
      CImg<T> res;
      const unsigned int siz = size();
      switch (siz) {
      case 1: break;
      case 3:
        res.assign(2,2);
        res(0,0) = (*this)(0);
        res(1,0) = res(0,1) = (*this)(1);
        res(1,1) = (*this)(2);
        break;
      case 6:
        res.assign(3,3);
        res(0,0) = (*this)(0);
        res(1,0) = res(0,1) = (*this)(1);
        res(2,0) = res(0,2) = (*this)(2);
        res(1,1) = (*this)(3);
        res(2,1) = res(1,2) = (*this)(4);
        res(2,2) = (*this)(5);
        break;
      default:
        throw CImgInstanceException("CImg<%s>::tensor() : Wrong vector dimension = %u in instance image.",
                                    pixel_type(), dim);
        break;
      }
      return res;
    }

    //! In-place version of the previous function.
    CImg<T>& tensor() {
      return get_tensor().transfer_to(*this);
    }

    //! Unroll all images values into specified axis.
    CImg<T> get_unroll(const char axe='x') const {
      return (+*this).unroll(axe);
    }

    //! In-place version of the previous function.
    CImg<T>& unroll(const char axe='x') {
      const unsigned int siz = size();
      if (siz) switch (axe) {
      case 'x': width = siz; height=depth=dim=1; break;
      case 'y': height = siz; width=depth=dim=1; break;
      case 'z': depth = siz; width=height=dim=1; break;
      case 'v': dim = siz; width=height=depth=1; break;
      default: throw CImgArgumentException("CImg<%s>::unroll() : Given axe is '%c' which is not 'x','y','z' or 'v'",
                                           pixel_type(),axe);
      }
      return *this;
    }

    //! Get a diagonal matrix, whose diagonal coefficients are the coefficients of the input image
    CImg<T> get_diagonal() const {
      if (is_empty()) return CImg<T>();
      CImg<T> res(size(),size(),1,1,0);
      cimg_foroff(*this,off) res(off,off) = (*this)(off);
      return res;
    }

    //! In-place version of the previous function.
    CImg<T>& diagonal() {
      return get_diagonal().transfer_to(*this);
    }

    //! Get an identity matrix having same dimension than instance image.
    CImg<T> get_identity_matrix() const {
      return identity_matrix(cimg::max(width,height));
    }

    //! In-place version of the previous function.
    CImg<T>& identity_matrix() {
      return identity_matrix(cimg::max(width,height)).transfer_to(*this);
    }

    //! Return a N-numbered sequence vector from \p a0 to \p a1
    CImg<T> get_sequence(const T a0, const T a1) const {
      return (+*this).sequence(a0,a1);
    }

    //! In-place version of the previous function.
    CImg<T>& sequence(const T a0, const T a1) {
      if (!is_empty()) {
        const unsigned int siz = size()-1;
        const float delta = (float)((float)a1-a0);
        T* ptr = data;
        cimg_foroff(*this,l) *(ptr++) = (T)(a0 + delta*l/siz);
      }
      return *this;
    }

    //! Return the transpose version of the current matrix.
    CImg<T> get_transpose() const {
      return get_permute_axes("yxzv");
    }

    //! In-place version of the previous function.
    CImg<T>& transpose() {
      if (width==1) { width=height; height=1; return *this; }
      if (height==1) { height=width; width=1; return *this; }
      if (width==height) {
        cimg_forYZV(*this,y,z,v) for (int x=y; x<dimx(); ++x) cimg::swap((*this)(x,y,z,v),(*this)(y,x,z,v));
        return *this;
      }
      return get_transpose().transfer_to(*this);
    }

    //! Return the inverse of the current matrix.
    CImg<typename cimg::superset<T,float>::type> get_invert(const bool use_LU=true) const {
      typedef typename cimg::superset<T,float>::type restype;
      return CImg<restype>(*this,false).invert(use_LU);
    }

    //! In-place version of the previous function.
    CImg<T>& invert(const bool use_LU=true) {
      if (!is_empty()) {
        if (width!=height || depth!=1 || dim!=1)
          throw CImgInstanceException("CImg<%s>::invert() : Instance matrix (%u,%u,%u,%u,%p) is not square.",
                                      pixel_type(),width,height,depth,dim,data);
#ifdef cimg_use_lapack
        typedef typename cimg::superset<T,float>::type ftype;
        int INFO = (int)use_LU, N = width, LWORK = 4*N, *IPIV = new int[N];
        ftype
          *lapA = new ftype[N*N],
          *WORK = new ftype[LWORK];
        cimg_forXY(*this,k,l) lapA[k*N+l] = (ftype)((*this)(k,l));
        cimg::getrf(N,lapA,IPIV,INFO);
        if (INFO) cimg::warn("CImg<%s>::invert() : LAPACK library function dgetrf_() returned error code %d.",pixel_type(),INFO);
        else {
          cimg::getri(N,lapA,IPIV,WORK,LWORK,INFO);
          if (INFO) cimg::warn("CImg<%s>::invert() : LAPACK library function dgetri_() returned Error code %d",pixel_type(),INFO);
        }
        if (!INFO) cimg_forXY(*this,k,l) (*this)(k,l) = (T)(lapA[k*N+l]); else fill(0);
        delete[] IPIV; delete[] lapA; delete[] WORK;
#else
        const double dete = width>3?-1.0:det();
        if (dete!=0.0 && width==2) {
          const double
            a = data[0], c = data[1],
            b = data[2], d = data[3];
          data[0] = (T)(d/dete); data[1] = (T)(-c/dete);
          data[2] = (T)(-b/dete); data[3] = (T)(a/dete);
        } else if (dete!=0.0 && width==3) {
          const double
            a = data[0], d = data[1], g = data[2],
            b = data[3], e = data[4], h = data[5],
            c = data[6], f = data[7], i = data[8];
          data[0] = (T)((i*e-f*h)/dete), data[1] = (T)((g*f-i*d)/dete), data[2] = (T)((d*h-g*e)/dete);
          data[3] = (T)((h*c-i*b)/dete), data[4] = (T)((i*a-c*g)/dete), data[5] = (T)((g*b-a*h)/dete);
          data[6] = (T)((b*f-e*c)/dete), data[7] = (T)((d*c-a*f)/dete), data[8] = (T)((a*e-d*b)/dete);
        } else {
          typedef typename cimg::superset<float,T>::type ftype;
          if (use_LU) { // LU-based inverse computation
            CImg<ftype> 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<ftype> 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;
    }

    //! Return the pseudo-inverse (Moore-Penrose) of the matrix
    CImg<typename cimg::superset<T,float>::type> get_pseudoinvert() const {
      typedef typename cimg::superset<T,float>::type restype;
      CImg<restype> U, S, V;
      SVD(U,S,V);
      cimg_forX(V,x) {
	const float s = S(x), invs = s!=0.0f?1.0f/s:0.0f;
	cimg_forY(V,y) V(x,y)*=invs;
      }
      return V*U.transpose();
    }

    //! In-place version of the previous function.
    CImg<T>& pseudoinvert() {
      return get_pseudoinvert().transfer_to(*this);
    }

    //! Compute the cross product between two 3d vectors.
    template<typename t> CImg<typename cimg::superset<T,t>::type> get_cross(const CImg<t>& img) const {
      typedef typename cimg::superset<T,t>::type restype;
      return CImg<restype>(*this).cross(img);
    }

    //! In-place version of the previous function.
    template<typename t> CImg<T>& cross(const CImg<t>& img) {
      if (width!=1 || height<3 || img.width!=1 || img.height<3)
        throw CImgInstanceException("CImg<%s>::cross() : Arguments (%u,%u,%u,%u,%p) and (%u,%u,%u,%u,%p) must be both 3d vectors.",
                                    pixel_type(),width,height,depth,dim,data,img.width,img.height,img.depth,img.dim,img.data);
      const T x = (*this)[0], y = (*this)[1], z = (*this)[2];
      (*this)[0] = (T)(y*img[2]-z*img[1]);
      (*this)[1] = (T)(z*img[0]-x*img[2]);
      (*this)[2] = (T)(x*img[1]-y*img[0]);
      return *this;
    }

    //! Solve a linear system AX=B where B=*this.
    template<typename t> CImg<typename cimg::superset2<T,t,float>::type> get_solve(const CImg<t>& A) const {
      typedef typename cimg::superset2<T,t,float>::type restype;
      return CImg<restype>(*this,false).solve(A);
    }

    //! In-place version of the previous function.
    template<typename t> CImg<T>& solve(const CImg<t>& A) {
      if (width!=1 || depth!=1 || dim!=1 || height!=A.height || A.depth!=1 || A.dim!=1)
        throw CImgArgumentException("CImg<%s>::solve() : Instance matrix size is (%u,%u,%u,%u) while "
                                    "size of given matrix A is (%u,%u,%u,%u).",
                                    pixel_type(),width,height,depth,dim,A.width,A.height,A.depth,A.dim);

      typedef typename cimg::superset2<T,t,float>::type ftype;
      if (A.width==A.height) {
#ifdef cimg_use_lapack
        char TRANS='N';
        int INFO, N = height, LWORK = 4*N, one = 1, *IPIV = new int[N];
        ftype
          *lapA = new ftype[N*N],
          *lapB = new ftype[N],
          *WORK = new ftype[LWORK];
        cimg_forXY(A,k,l) lapA[k*N+l] = (ftype)(A(k,l));
        cimg_forY(*this,i) lapB[i] = (ftype)((*this)(i));
        cimg::getrf(N,lapA,IPIV,INFO);
        if (INFO) cimg::warn("CImg<%s>::solve() : LAPACK library function dgetrf_() returned error code %d.",pixel_type(),INFO);
        if (!INFO) {
          cimg::getrs(TRANS,N,lapA,IPIV,lapB,INFO);
          if (INFO) cimg::warn("CImg<%s>::solve() : LAPACK library function dgetrs_() returned Error code %d",pixel_type(),INFO);
        }
        if (!INFO) cimg_forY(*this,i) (*this)(i) = (T)(lapB[i]); else fill(0);
        delete[] IPIV; delete[] lapA; delete[] lapB; delete[] WORK;
#else
        CImg<ftype> lu(A);
        CImg<ftype> indx;
        bool d;
        lu._LU(indx,d);
        _solve(lu,indx);
#endif
      } else assign(A.get_pseudoinvert()*(*this));
      return *this;
    }

    template<typename t, typename ti> CImg<T>& _solve(const CImg<t>& A, const CImg<ti>& indx) {
      typedef typename cimg::superset2<T,t,float>::type ftype;
      const int N = size();
      int ii = -1;
      ftype sum;
      for (int i=0; i<N; ++i) {
        const int ip = (int)indx[i];
        ftype 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;
    }

    //! Sort values of a vector and get permutations.
    template<typename t> CImg<T> get_sort(CImg<t>& permutations, const bool increasing=true) const {
      return (+*this).sort(permutations,increasing);
    }

    //! In-place version of the previous function.
    template<typename t>
    CImg<T>& sort(CImg<t>& permutations, const bool increasing=true) {
      if (is_empty()) permutations.assign();
      else {
        if (permutations.size()!=size()) permutations.assign(size());
        cimg_foroff(permutations,off) permutations[off] = (t)off;
        _quicksort(0,size()-1,permutations,increasing);
      }
      return *this;
    }

    // Sort image values
    CImg<T> get_sort(const bool increasing=true) const {
      return (+*this).sort(increasing);
    }

    //! In-place version of the previous function.
    CImg<T>& sort(const bool increasing=true) {
      CImg<T> foo;
      return sort(foo,increasing);
    }

    template<typename t> CImg<T>& _quicksort(const int min, const int max, CImg<t>& permutations, const bool increasing) {
      if (min<max) {
        const int mid = (min+max)/2;
        if (increasing) {
          if ((*this)[min]>(*this)[mid]) {
            cimg::swap((*this)[min],(*this)[mid]); cimg::swap(permutations[min],permutations[mid]); }
          if ((*this)[mid]>(*this)[max]) {
            cimg::swap((*this)[max],(*this)[mid]); cimg::swap(permutations[max],permutations[mid]); }
          if ((*this)[min]>(*this)[mid]) {
            cimg::swap((*this)[min],(*this)[mid]); cimg::swap(permutations[min],permutations[mid]); }
        } else {
          if ((*this)[min]<(*this)[mid]) {
            cimg::swap((*this)[min],(*this)[mid]); cimg::swap(permutations[min],permutations[mid]); }
          if ((*this)[mid]<(*this)[max]) {
            cimg::swap((*this)[max],(*this)[mid]); cimg::swap(permutations[max],permutations[mid]); }
          if ((*this)[min]<(*this)[mid]) {
            cimg::swap((*this)[min],(*this)[mid]); cimg::swap(permutations[min],permutations[mid]); }
        }
        if (max-min>=3) {
          const T pivot = (*this)[mid];
          int i = min, j = max;
          if (increasing) {
            do {
              while ((*this)[i]<pivot) ++i;
              while ((*this)[j]>pivot) --j;
              if (i<=j) {
                cimg::swap((*this)[i],(*this)[j]);
                cimg::swap(permutations[i++],permutations[j--]);
              }
            } while (i<=j);
          } else {
            do {
              while ((*this)[i]>pivot) ++i;
              while ((*this)[j]<pivot) --j;
              if (i<=j) {
                cimg::swap((*this)[i],(*this)[j]);
                cimg::swap(permutations[i++],permutations[j--]);
              }
            } while (i<=j);
          }
          if (min<j) _quicksort(min,j,permutations,increasing);
          if (i<max) _quicksort(i,max,permutations,increasing);
        }
      }
      return *this;
    }

    //! Get a permutation of the pixels
    template<typename t> CImg<T> get_permute(const CImg<t>& permutation) const {
      if (permutation.size()!=size())
        throw CImgArgumentException("CImg<%s>::permute() : Instance image (%u,%u,%u,%u,%p) and permutation (%u,%u,%u,%u,%p)"
                                    "have different sizes.",pixel_type(),
                                    width,height,depth,dim,data,
                                    permutation.width,permutation.height,permutation.depth,permutation.dim,permutation.data);
      CImg<T> res(width,height,depth,dim);
      const t *p = permutation.ptr(permutation.size());
      cimg_for(res,ptr,T) *ptr = (*this)[*(--p)];
      return res;
    }

    //! In-place version of the previous function.
    template<typename t> CImg<T>& permute(const CImg<t>& permutation) {
      return get_permute(permutation).transfer_to(*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_iter=40, const float lambda=0) const {
      if (is_empty()) { U.assign(); S.assign(); V.assign(); }
      else {
#ifdef cimg_use_lapack
        typedef typename cimg::superset<T,float>::type ftype;
        int M = height, N = width, mn = cimg::min(M,N), MN = cimg::max(M,N), LWORK = 4*mn+2*MN, INFO;
        char JOB='A';
        ftype
          *lapA = new ftype[M*N],
          *lapS = new ftype[mn],
          *lapU = new ftype[M*M],
          *lapV = new ftype[N*N],
          *WORK = new ftype[LWORK];
        cimg_forXY(*this,k,l) lapA[k*N+l] = (ftype)((*this)(k,l));
        cimg::gesvd(JOB,M,N,lapA,MN,lapS,lapU,lapV,WORK,LWORK,INFO);
        if (INFO) cimg::warn("CImg<%s>::SVD() : LAPACK library function gesvd_() returned error code %d.",pixel_type(),INFO);
        U.assign(M,M);
        S.assign(1,mn);
        V.assign(N,N);
        if (!INFO) {
          cimg_forY(S,i) S(i) = (T)(lapS[i]);
          cimg_forXY(U,p,q) U(p,q) = (T)(lapU[p*N+q]);
          cimg_forXY(V,k,l) V(l,k) = (T)(lapV[k*N+l]);
        } else { S.fill(0); U.fill(0); V.fill(0); }
        delete[] lapA; delete[] lapS; delete[] lapU; delete[] lapV; delete[] WORK;
#else
        U = *this;
	if (lambda!=0.0f) {
	  const unsigned int delta = cimg::min(U.width,U.height);
	  for (unsigned int i=0; i<delta; ++i) U(i,i) = (t)(U(i,i) + lambda);
	}
        if (S.size()<width) S.assign(1,width);
        if (V.width<width || V.height<height) V.assign(width,width);
        CImg<t> rv1(width);
        t anorm = 0, c, f, g = 0, h, s, scale = 0;
        int l = 0, nm = 0;

        cimg_forX(U,i) {
          l = i+1; rv1[i] = scale*g; g = s = scale = 0;
          if (i<dimy()) {
            for (int k=i; k<dimy(); ++k) scale+= cimg::abs(U(i,k));
            if (scale) {
              for (int k=i; k<dimy(); ++k) { U(i,k)/=scale; s+= U(i,k)*U(i,k); }
              f = U(i,i); g = (t)((f>=0?-1:1)*std::sqrt(s)); h=f*g-s; U(i,i) = f-g;
              for (int j=l; j<dimx(); ++j) {
                s = 0; for (int k=i; k<dimy(); ++k) s+= U(i,k)*U(j,k);
                f = s/h;
                { for (int k=i; k<dimy(); ++k) U(j,k)+= f*U(i,k); }
              }
              { for (int k=i; k<dimy(); ++k) U(i,k)*= scale; }
            }
          }
          S[i]=scale*g;

          g = s = scale = 0;
          if (i<dimy() && i!=dimx()-1) {
            for (int k=l; k<dimx(); ++k) scale += cimg::abs(U(k,i));
            if (scale) {
              for (int k=l; k<dimx(); ++k) { U(k,i)/= scale; s+= U(k,i)*U(k,i); }
              f = U(l,i); g = (t)((f>=0?-1:1)*std::sqrt(s)); h = f*g-s; U(l,i) = f-g;
              { for (int k=l; k<dimx(); ++k) rv1[k]=U(k,i)/h; }
              for (int j=l; j<dimy(); ++j) {
                s = 0; for (int k=l; k<dimx(); ++k) s+= U(k,j)*U(k,i);
                { for (int k=l; k<dimx(); ++k) U(k,j)+= s*rv1[k]; }
              }
              { for (int k=l; k<dimx(); ++k) U(k,i)*= scale; }
            }
          }
          anorm = (t)cimg::max((float)anorm,(float)(cimg::abs(S[i])+cimg::abs(rv1[i])));
        }

        { for (int i=dimx()-1; i>=0; --i) {
          if (i<dimx()-1) {
            if (g) {
              { for (int j=l; j<dimx(); ++j) V(i,j) =(U(j,i)/U(l,i))/g; }
              for (int j=l; j<dimx(); ++j) {
                s = 0; for (int k=l; k<dimx(); ++k) s+= U(k,i)*V(j,k);
                { for (int k=l; k<dimx(); ++k) V(j,k)+= s*V(i,k); }
              }
            }
            for (int j=l; j<dimx(); ++j) V(j,i) = V(i,j) = (t)0.0;
          }
          V(i,i) = (t)1.0; g = rv1[i]; l = i;
        }
        }

        { for (int i=cimg::min(dimx(),dimy())-1; i>=0; --i) {
          l = i+1; g = S[i];
          for (int j=l; j<dimx(); ++j) U(j,i) = 0;
          if (g) {
            g = 1/g;
            for (int j=l; j<dimx(); ++j) {
              s = 0; for (int k=l; k<dimy(); ++k) s+= U(i,k)*U(j,k);
              f = (s/U(i,i))*g;
              { for (int k=i; k<dimy(); ++k) U(j,k)+= f*U(i,k); }
            }
            { for (int j=i; j<dimy(); ++j) U(i,j)*= g; }
          } else for (int j=i; j<dimy(); ++j) U(i,j) = 0;
          ++U(i,i);
        }
        }

        for (int k=dimx()-1; k>=0; --k) {
          for (unsigned int its=0; its<max_iter; ++its) {
            bool flag = true;
            for (l=k; l>=1; --l) {
              nm = l-1;
              if ((cimg::abs(rv1[l])+anorm)==anorm) { flag = false; break; }
              if ((cimg::abs(S[nm])+anorm)==anorm) break;
            }
            if (flag) {
              c = 0; s = 1;
              for (int i=l; i<=k; ++i) {
                f = s*rv1[i]; rv1[i] = c*rv1[i];
                if ((cimg::abs(f)+anorm)==anorm) break;
                g = S[i]; h = (t)cimg::pythagore(f,g); S[i] = h; h = 1/h; c = g*h; s = -f*h;
                cimg_forY(U,j) { const t y = U(nm,j), z = U(i,j); U(nm,j) = y*c+z*s; U(i,j) = z*c-y*s; }
              }
            }
            const t z = S[k];
            if (l==k) { if (z<0) { S[k] = -z; cimg_forX(U,j) V(k,j) = -V(k,j); } break; }
            nm = k-1;
            t x = S[l], y = S[nm];
            g = rv1[nm]; h = rv1[k];
            f = ((y-z)*(y+z)+(g-h)*(g+h))/(2*h*y);
            g = (t)cimg::pythagore(f,1.0);
            f = ((x-z)*(x+z)+h*((y/(f+ (f>=0?g:-g)))-h))/x;
            c = s = 1;
            for (int j=l; j<=nm; ++j) {
              const int i = j+1;
              g = rv1[i]; h = s*g; g = c*g;
              t y = S[i];
              t z = (t)cimg::pythagore(f,h);
              rv1[j] = z; c = f/z; s = h/z;
              f = x*c+g*s; g = g*c-x*s; h = y*s; y*=c;
              cimg_forX(U,jj) { const t x = V(j,jj), z = V(i,jj); V(j,jj) = x*c+z*s; V(i,jj) = z*c-x*s; }
              z = (t)cimg::pythagore(f,h); S[j] = z;
              if (z) { z = 1/z; c = f*z; s = h*z; }
              f = c*g+s*y; x = c*y-s*g;
              { cimg_forY(U,jj) { const t y = U(j,jj); z = U(i,jj); U(j,jj) = y*c+z*s; U(i,jj) = z*c-y*s; }}
            }
            rv1[l] = 0; rv1[k]=f; S[k]=x;
          }
        }

        if (sorting) {
          CImg<int> permutations(width);
          CImg<t> tmp(width);
          S.sort(permutations,false);
          cimg_forY(U,k) {
            cimg_forX(permutations,x) tmp(x) = U(permutations(x),k);
            std::memcpy(U.ptr(0,k),tmp.data,sizeof(t)*width);
          }
          { cimg_forY(V,k) {
            cimg_forX(permutations,x) tmp(x) = V(permutations(x),k);
            std::memcpy(V.ptr(0,k),tmp.data,sizeof(t)*width);
          }}
        }
#endif
      }
    return *this;
    }

    //! Compute the SVD of a general matrix.
    template<typename t> const CImg<T>& SVD(CImgList<t>& USV) const {
      if (USV.size<3) USV.assign(3);
      return SVD(USV[0],USV[1],USV[2]);
    }

    //! Compute the SVD of a general matrix.
    CImgList<typename cimg::superset<T,float>::type> get_SVD(const bool sorting=true) const {
      typedef typename cimg::superset<T,float>::type restype;
      CImgList<restype> res(3);
      SVD(res[0],res[1],res[2],sorting);
      return res;
    }

    // INNER ROUTINE : Compute the LU decomposition of a permuted matrix (c.f. numerical recipies)
    template<typename t> CImg<T>& _LU(CImg<t>& indx, bool& d) {
      typedef typename cimg::superset<T,float>::type ftype;
      const int N = dimx();
      int imax = 0;
      CImg<ftype> vv(N);
      indx.assign(N);
      d = true;
      cimg_forX(*this,i) {
        ftype vmax = 0;
        cimg_forX(*this,j) {
          const ftype 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) {
          ftype sum=(*this)(j,i);
          for (int k=0; k<i; ++k) sum-=(*this)(k,i)*(*this)(j,k);
          (*this)(j,i) = (T)sum;
        }
        ftype vmax = 0;
        { for (int i=j; i<dimx(); ++i) {
          ftype sum=(*this)(j,i);
          for (int k=0; k<j; ++k) sum-=(*this)(k,i)*(*this)(j,k);
          (*this)(j,i) = (T)sum;
          const ftype 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 ftype tmp = 1/(ftype)(*this)(j,j);
          for (int i=j+1; i<N; ++i) (*this)(j,i) = (T)((*this)(j,i)*tmp);
        }
      }
      return *this;
    }

    //! Return the eigenvalues and eigenvectors of a matrix.
    CImgList<typename cimg::superset<T,float>::type> get_eigen() const {
      typedef typename cimg::superset<T,float>::type restype;
      CImgList<restype> res(2);
      eigen(res[0],res[1]);
      return res;
    }

    //! Compute the eigenvalues and eigenvectors of a matrix.
    template<typename t> const CImg<T>& eigen(CImg<t>& val, CImg<t> &vec) const {
      if (is_empty()) { val.assign(); vec.assign(); }
      else {
        if (width!=height || depth>1 || dim>1)
          throw CImgInstanceException("CImg<%s>::eigen() : Instance object (%u,%u,%u,%u,%p) is empty.",
                                      pixel_type(),width,height,depth,dim,data);
        if (val.size()<width) val.assign(1,width);
        if (vec.size()<width*width) vec.assign(width,width);
        switch(width) {
        case 1: { val[0]=(t)(*this)[0]; vec[0]=(t)1; } break;
        case 2: {
          const double a = (*this)[0], b = (*this)[1], c = (*this)[2], d = (*this)[3], e = a+d;
          double f = e*e-4*(a*d-b*c);
          if (f<0) cimg::warn("CImg<%s>::eigen() : Complex eigenvalues",pixel_type());
          f = 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<%s>::eigen() : Eigenvalues computation of general matrices is limited"
                                      "to 2x2 matrices (given is %ux%u)", pixel_type(),width,height);
        }
      }
      return *this;
    }

    //! Compute the eigenvalues and eigenvectors of a symmetric matrix.
    CImgList<typename cimg::superset<T,float>::type> get_symmetric_eigen() const {
      typedef typename cimg::superset<T,float>::type restype;
      CImgList<restype> res(2);
      symmetric_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
        typedef typename cimg::superset<T,float>::type ftype;
        char JOB = 'V', UPLO = 'U';
        int N = width, LWORK = 4*N, INFO;
        ftype
          *lapA = new ftype[N*N],
          *lapW = new ftype[N],
          *WORK = new ftype[LWORK];
        cimg_forXY(*this,k,l) lapA[k*N+l] = (ftype)((*this)(k,l));
        cimg::syev(JOB,UPLO,N,lapA,lapW,WORK,LWORK,INFO);
        if (INFO) cimg::warn("CImg<%s>::symmetric_eigen() : LAPACK library function dsyev_() returned error code %d.",pixel_type(),INFO);
        val.assign(1,N);
	vec.assign(N,N);
        if (!INFO) {
          cimg_forY(val,i) val(i) = (T)lapW[N-1-i];
          cimg_forXY(vec,k,l) vec(k,l) = (T)(lapA[(N-1-k)*N+l]);
        } else { val.fill(0); vec.fill(0); }
        delete[] lapA; delete[] lapW; delete[] WORK;
#else
        if (width!=height || depth>1 || dim>1)
          throw CImgInstanceException("CImg<%s>::eigen() : Instance object (%u,%u,%u,%u,%p) is empty.",
                                      pixel_type(),width,height,depth,dim,data);
	val.assign(1,width);
	if (vec.data) vec.assign(width,width);
        if (width<3) return eigen(val,vec);
        CImg<t> V(width,width);
        SVD(vec,val,V,false);
	bool ambiguous = false;
	float eig = 0;
	cimg_forY(val,p) {       // check for ambiguous cases.
	  if (val[p]>eig) eig = (float)val[p];
          t scal = 0;
          cimg_forY(vec,y) scal+=vec(p,y)*V(p,y);
          if (cimg::abs(scal)<0.9f) ambiguous = true;
          if (scal<0) val[p] = -val[p];
        }
	if (ambiguous) {
	  (eig*=2)++;
	  SVD(vec,val,V,false,40,eig);
	  val-=eig;
	}
        CImg<int> permutations(width);  // sort eigenvalues in decreasing order
        CImg<t> tmp(width);
        val.sort(permutations,false);
        cimg_forY(vec,k) {
          cimg_forX(permutations,x) tmp(x) = vec(permutations(x),k);
          std::memcpy(vec.ptr(0,k),tmp.data,sizeof(t)*width);
        }
#endif
      }
      return *this;
    }

    //@}
    //-------------------
    //
    //! \name Display
    //@{
    //-------------------

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

    //! Display an image in a window with a title \p title, and wait a 'is_closed' or 'keyboard' event.\n
    const CImg<T>& display(const char *const title=0, const bool display_info=true) const {
      if (is_empty()) throw CImgInstanceException("CImg<%s>::display() : Instance image (%u,%u,%u,%u,%p) is empty.",
                                                  pixel_type(),width,height,depth,dim,data);
      unsigned int imgw = width + (depth>1?depth:0), imgh = height + (depth>1?depth:0), oldw = 0, oldh = 0, XYZ[3];
      int x0 = 0, y0 = 0, z0 = 0, x1 = dimx()-1, y1 = dimy()-1, z1 = dimz()-1, frametiming = 200;
      CImgDisplay::_fitscreen(imgw,imgh);
      char ntitle[64] = { 0 }; if (!title) std::sprintf(ntitle,"CImg<%s>",pixel_type());
      if (display_info) print(title?title:ntitle);
      CImgDisplay disp(imgw,imgh,title?title:ntitle,3,3);
      CImg<T> zoom;
      for (bool stopflag = false, reset_view = true, resize_disp = false; !stopflag && !disp.is_closed; ) {
        if (reset_view) {
          XYZ[0] = (x0+x1)/2; XYZ[1] = (y0+y1)/2; XYZ[2] = (z0+z1)/2;
          x0 = 0; y0 = 0; z0 = 0; x1 = width-1; y1 = height-1; z1 = depth-1;
          oldw = disp.width; oldh = disp.height;
          reset_view = false;
        }
        if (!x0 && !y0 && !z0 && x1==dimx()-1 && y1==dimy()-1 && z1==dimz()-1) zoom.assign();
        else zoom = get_crop(x0,y0,z0,x1,y1,z1);

        const unsigned int
          dx = 1 + x1 - x0, dy = 1 + y1 - y0, dz = 1 + z1 - z0,
          tw = dx + (dz>1?dz:0), th = dy + (dz>1?dz:0);
        if (resize_disp) {
          const unsigned int ttw = tw*disp.width/oldw, tth = th*disp.height/oldh;
          const unsigned int dM = cimg::max(ttw,tth), diM = cimg::max(disp.width,disp.height);
          imgw = cimg::max(16U,ttw*diM/dM); imgh = cimg::max(16U,tth*diM/dM);
          CImgDisplay::_fitscreen(imgw,imgh);
          if (disp.is_fullscreen) disp.toggle_fullscreen();
          disp.resize(imgw,imgh,false);
          resize_disp = false;
        }
        oldw = tw; oldh = th;

        const CImg<T>& visu = zoom?zoom:*this;
        const CImg<int> selection = visu.get_coordinates(2,disp,XYZ,0);
        bool go_up = false, go_down = false, go_left = false, go_right = false;
        const int
          sx0 = selection(0), sy0 = selection(1), sz0 = selection(2),
          sx1 = selection(3), sy1 = selection(4), sz1 = selection(5);
        if (sx0>=0 && sy0>=0 && sz0>=0 && sx1>=0 && sy1>=0 && sz1>=0) {
          x1 = x0 + sx1; y1 = y0 + sy1; z1 = z0 + sz1; x0+=sx0; y0+=sy0; z0+=sz0;
          if (sx0==sx1 && sy0==sy1 && sz0==sz1) reset_view = true;
          resize_disp = true;
        } else switch (disp.key) {

        case cimg::keyP: { // Special mode : play stack of frames
          if (visu.depth>1) {
            unsigned int
              w = visu.width*disp.width/(visu.width+(visu.depth>1?visu.depth:0)),
              h = visu.height*disp.height/(visu.height+(visu.depth>1?visu.depth:0));
            CImgDisplay::_fitscreen(w,h);
            disp.resize(w,h,false).key = 0;
            while (!stopflag && !disp.is_closed && !disp.button) {
              if (disp.is_resized) disp.resize();
              visu.get_slice(XYZ[2]).display(disp.set_title("%s (z = %d)",title?title:ntitle,XYZ[2]));
              if (disp.wheel) { frametiming-=10*disp.wheel; disp.wheel = 0; }
              if (disp.key) {
                stopflag = true;
                if (disp.key==cimg::keyPAGEUP) { frametiming-=50; disp.key = 0; stopflag = false; }
                if (disp.key==cimg::keyPAGEDOWN) { frametiming+=50; disp.key = 0; stopflag = false; }
                if (disp.key==cimg::keyCTRLLEFT) stopflag = false;
                if (disp.key!=cimg::keyCTRLLEFT && disp.is_key(cimg::keyCTRLLEFT,true)) {
                  switch (disp.key) {
                  case cimg::keyD: {
                    if (disp.is_fullscreen) disp.toggle_fullscreen();
                    unsigned int nw = 2*disp.width, nh = 2*disp.height;
                    CImgDisplay::_fitscreen(nw,nh);
                    disp.resize(nw,nh,false).key = 0;
                  } break;
                  case cimg::keyC: {
                    if (disp.is_fullscreen) disp.toggle_fullscreen();
                    unsigned int nw = disp.width/2, nh = disp.height/2;
                    CImgDisplay::_fitscreen(nw,nh);
                    disp.resize(nw,nh,false).key = 0;
                  } break;
                  case cimg::keyR: {
                    if (disp.is_fullscreen) disp.toggle_fullscreen();
                    unsigned int nw = width + (depth>1?depth:0), nh = height + (depth>1?depth:0);
                    CImgDisplay::_fitscreen(nw,nh);
                    disp.resize(nw,nh,false).key = 0;
                  } break;
                  case cimg::keyF:
                    disp.resize(disp.screen_dimx(),disp.screen_dimy()).toggle_fullscreen().key = 0;
                    break;
                  default: disp.key = 0;
                  }
                  stopflag = false;
                }
                frametiming = cimg::min(1000,cimg::max(1,frametiming));
              } else {
                if (++XYZ[2]>=visu.depth) XYZ[2] = 0;
                disp.wait(frametiming);
              }
            }
            w = (visu.width + (visu.depth>1?visu.depth:0))*disp.width/visu.width;
            h = (visu.height + (visu.depth>1?visu.depth:0))*disp.height/visu.height;
            CImgDisplay::_fitscreen(w,h);
            disp.resize(w,h,false).set_title(title?title:ntitle);
            disp.key = disp.button = disp.wheel = 0;
          }
        } break;
        case cimg::keyHOME: case cimg::keyBACKSPACE: reset_view = resize_disp = true; break;
        case cimg::keyPADADD: {
          const int deltax = (x1-x0)/8, deltay = (y1-y0)/8, deltaz = (z1-z0)/8;
          x0+=deltax; y0+=deltay; z0+=deltaz;
          x1-=deltax; y1-=deltay; z1-=deltaz;
        } break;
        case cimg::keyPADSUB: {
          const int
            deltax = (x1-x0)/8, deltay = (y1-y0)/8, deltaz = (z1-z0)/8,
            ndeltax = deltax?deltax:(width>1?1:0),
            ndeltay = deltay?deltay:(height>1?1:0),
            ndeltaz = deltaz?deltaz:(depth>1?1:0);
          x0-=ndeltax; y0-=ndeltay; z0-=ndeltaz;
          x1+=ndeltax; y1+=ndeltay; z1+=ndeltaz;
          if (x0<0) { x1-=x0; x0 = 0; if (x1>=dimx()) x1 = dimx()-1; }
          if (y0<0) { y1-=y0; y0 = 0; if (y1>=dimy()) y1 = dimy()-1; }
          if (z0<0) { z1-=z0; z0 = 0; if (z1>=dimz()) z1 = dimz()-1; }
          if (x1>=dimx()) { x0-=(x1-dimx()+1); x1 = dimx()-1; if (x0<0) x0 = 0; }
          if (y1>=dimy()) { y0-=(y1-dimy()+1); y1 = dimy()-1; if (y0<0) y0 = 0; }
          if (z1>=dimz()) { z0-=(z1-dimz()+1); z1 = dimz()-1; if (z0<0) z0 = 0; }
        } break;
        case cimg::keyARROWLEFT: case cimg::keyPAD4: go_left = true; break;
        case cimg::keyARROWRIGHT: case cimg::keyPAD6: go_right = true; break;
        case cimg::keyARROWUP: case cimg::keyPAD8: go_up = true; break;
        case cimg::keyARROWDOWN: case cimg::keyPAD2: go_down = true; break;
        case cimg::keyPAD7: go_up = go_left = true; break;
        case cimg::keyPAD9: go_up = go_right = true; break;
        case cimg::keyPAD1: go_down = go_left = true; break;
        case cimg::keyPAD3: go_down = go_right = true; break;
        case cimg::keyPAGEUP: {
          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; }
        } break;
        case cimg::keyPAGEDOWN: {
          const int delta = (z1-z0)/5, ndelta = delta?delta:(depth>1?1:0);
          if (z1+ndelta<dimz()) { z0+=ndelta; z1+=ndelta; }
          else { z0+=(depth-1-z1); z1 = depth-1; }
        } break;
        case 0: case cimg::keyPAD5: break;
        default: stopflag = true; break;
        }

        if (go_left) {
          const int delta = (x1-x0)/5, ndelta = delta?delta:(width>1?1:0);
          if (x0-ndelta>=0) { x0-=ndelta; x1-=ndelta; }
          else { x1-=x0; x0 = 0; }
        }
        if (go_right) {
          const int delta = (x1-x0)/5, ndelta = delta?delta:(width>1?1:0);
          if (x1+ndelta<dimx()) { x0+=ndelta; x1+=ndelta; }
          else { x0+=(dimx()-1-x1); x1 = dimx()-1; }
        }
        if (go_up) {
          const int delta = (y1-y0)/5, ndelta = delta?delta:(height>1?1:0);
          if (y0-ndelta>=0) { y0-=ndelta; y1-=ndelta; }
          else { y1-=y0; y0 = 0; }
        }
        if (go_down) {
          const int delta = (y1-y0)/5, ndelta = delta?delta:(height>1?1:0);
          if (y1+ndelta<dimy()) { y0+=ndelta; y1+=ndelta; }
          else { y0+=(dimy()-1-y1); y1 = dimy()-1; }
        }
      }
      return *this;
    }

    //! Simple interface to select shaped from an image
    /**
       \param selection  Array of 6 values containing the selection result
       \param coords_type Determine shape type to select (0=point, 1=vector, 2=rectangle, 3=circle)
       \param disp       Display window used to make the selection
       \param XYZ        Initial XYZ position (for volumetric images only)
       \param color      Color of the shape selector.
    **/
    CImg<typename cimg::last<T,int>::type> get_coordinates(const int coords_type, CImgDisplay &disp,
                                                           unsigned int *const XYZ=0, const unsigned char *const color=0) const {

      typedef typename cimg::last<T,unsigned char>::type uchar;
      if (is_empty()) throw CImgInstanceException("CImg<%s>::get_coordinates() : Instance image (%u,%u,%u,%u,%p) is empty.",
						  pixel_type(),width,height,depth,dim,data);
      if (!disp) throw CImgArgumentException("CImg<%s>::get_coordinates() : Specified display is empty.",pixel_type());

      const unsigned int
        old_events = disp.events,
        old_normalization = disp.normalization,
        hatch = 0x55555555;

      bool old_is_resized = disp.is_resized;
      disp.events = 3;
      disp.normalization = 0;
      disp.show().key = 0;

      unsigned char fgcolor[] = { 255,255,105 }, bgcolor[] = { 0,0,0 };
      if (color) std::memcpy(fgcolor,color,sizeof(unsigned char)*cimg::min(3,dimv()));

      int area = 0, clicked_area = 0, phase = 0,
        X0 = (int)((XYZ?XYZ[0]:width/2)%width), Y0 = (int)((XYZ?XYZ[1]:height/2)%height), Z0 = (int)((XYZ?XYZ[2]:depth/2)%depth),
        X1 =-1, Y1 = -1, Z1 = -1,
        X = -1, Y = -1, Z = -1,
        oX = X, oY = Y, oZ = Z;
      unsigned int old_button = 0, key = 0;

      bool shape_selected = false, text_down = false;
      CImg<uchar> visu, visu0;
      char text[1024] = { 0 };

      while (!key && !disp.is_closed && !shape_selected) {

        // Handle mouse motion and selection
        oX = X; oY = Y; oZ = Z;
        int mx = disp.mouse_x, my = disp.mouse_y;
        const int mX = mx*(width+(depth>1?depth:0))/disp.width, mY = my*(height+(depth>1?depth:0))/disp.height;

        area = 0;
        if (mX<dimx() && mY<dimy())  { area = 1; X = mX; Y = mY; Z = phase?Z1:Z0; }
        if (mX<dimx() && mY>=dimy()) { area = 2; X = mX; Z = mY-height; Y = phase?Y1:Y0; }
        if (mX>=dimx() && mY<dimy()) { area = 3; Y = mY; Z = mX-width; X = phase?X1:X0; }

        key = disp.key;
        if (key && key!=cimg::keyCTRLLEFT) {
          switch (key) {
          case cimg::keyPAGEUP: ++disp.wheel; break;
          case cimg::keyPAGEDOWN: --disp.wheel; break;
          }
          if (disp.is_key(cimg::keyCTRLLEFT,true)) {
            switch (key) {
            case cimg::keyD: {
              if (disp.is_fullscreen) disp.toggle_fullscreen();
              unsigned int nw = 2*disp.width, nh = 2*disp.height;
              CImgDisplay::_fitscreen(nw,nh);
              disp.resize(nw,nh);
              disp.is_resized = true;
            } break;
            case cimg::keyC: {
              if (disp.is_fullscreen) disp.toggle_fullscreen();
              unsigned int nw = disp.width/2, nh = disp.height/2;
              CImgDisplay::_fitscreen(nw,nh);
              disp.resize(nw,nh);
              disp.is_resized = true;
            } break;
            case cimg::keyR: {
              if (disp.is_fullscreen) disp.toggle_fullscreen();
              unsigned int nw = width + (depth>1?depth:0), nh = height + (depth>1?depth:0);
              CImgDisplay::_fitscreen(nw,nh);
              disp.resize(nw,nh);
              disp.is_resized = true;
            } break;
            case cimg::keyF:
              disp.resize(disp.screen_dimx(),disp.screen_dimy()).toggle_fullscreen();
              disp.is_resized = true;
              break;
            case cimg::keyS: {
              static unsigned int snap_number = 0;
              char filename[32] = { 0 };
              std::FILE *file;
              do {
                std::sprintf(filename,"CImg_%.4u.bmp",snap_number++);
                if ((file=std::fopen(filename,"r"))!=0) std::fclose(file);
              } while (file);
              if (visu0) {
                visu.draw_text(2,2,fgcolor,bgcolor,11,0.8f,"Saving snapshot...").display(disp);
                visu0.save(filename);
                visu.draw_text(2,2,fgcolor,bgcolor,11,0.8f,"Snapshot '%s' saved.",filename).display(disp);
              }
            } break;
            case cimg::keyO: {
              static unsigned int snap_number = 0;
              char filename[32] = { 0 };
              std::FILE *file;
              do {
                std::sprintf(filename,"CImg_%.4u.cimg",snap_number++);
                if ((file=std::fopen(filename,"r"))!=0) std::fclose(file);
              } while (file);
              visu.draw_text(2,2,fgcolor,bgcolor,11,0.8f,"Saving instance...").display(disp);
              save(filename);
              visu.draw_text(2,2,fgcolor,bgcolor,11,0.8f,"Instance '%s' saved.",filename).display(disp);
            } break;
            default: break;
            }
            key = disp.key = 0;
          }
        } else key = 0;

        if (!area) mx = my = X = Y = Z = -1;
        else {
          if (disp.button&1 && phase<2) { X1 = X; Y1 = Y; Z1 = Z; }
          if (!(disp.button&1) && phase>=2) {
            switch (clicked_area) {
            case 1: Z1 = Z; break;
            case 2: Y1 = Y; break;
            case 3: X1 = X; break;
            }
          }
          if (disp.button&2) { if (phase) { X1 = X; Y1 = Y; Z1 = Z; } else { X0 = X; Y0 = Y; Z0 = Z; } }
          if (disp.button&4) { oX = X = X0; oY = Y = Y0; oZ = Z = Z0; phase = 0; visu.assign(); }
          if (disp.wheel) {
            switch (area) {
            case 1: if (phase) Z = (Z1+=disp.wheel); else Z = (Z0+=disp.wheel); break;
            case 2: if (phase) Y = (Y1+=disp.wheel); else Y = (Y0+=disp.wheel); break;
            case 3: if (phase) X = (X1+=disp.wheel); else X = (X0+=disp.wheel); break;
            default: break;
            }
            disp.wheel = 0;
          }
          if ((disp.button&1)!=old_button) {
            switch (phase++) {
            case 0: X0 = X1 = X; Y0 = Y1 = Y; Z0 = Z1 = Z; clicked_area = area; break;
            case 1: X1 = X; Y1 = Y; Z1 = Z; break;
            default: break;
            }
            old_button = disp.button&1;
          }
          if (depth>1 && (X!=oX || Y!=oY || Z!=oZ)) visu0.assign();
        }

        if (phase) {
          if (!coords_type) shape_selected = phase?true:false;
          else {
            if (depth>1) shape_selected = (phase==3)?true:false;
            else shape_selected = (phase==2)?true:false;
          }
        }

        if (X0<0) X0 = 0; if (X0>=dimx()) X0 = dimx()-1; if (Y0<0) Y0 = 0; if (Y0>=dimy()) Y0 = dimy()-1;
        if (Z0<0) Z0 = 0; if (Z0>=dimz()) Z0 = dimz()-1;
        if (X1<1) X1 = 0; if (X1>=dimx()) X1 = dimx()-1; if (Y1<0) Y1 = 0; if (Y1>=dimy()) Y1 = dimy()-1;
        if (Z1<0) Z1 = 0; if (Z1>=dimz()) Z1 = dimz()-1;

        // Draw visualization image on the display
        if (oX!=X || oY!=Y || oZ!=Z || !visu0) {
          if (!visu0) {
            typedef typename cimg::superset<T,unsigned char>::type nT;     // avoid problems with 'bool' images
            CImg<nT> tmp, tmp0;
            if (depth!=1) {
              tmp0 = (!phase)?get_projections2d(X0,Y0,Z0):get_projections2d(X1,Y1,Z1);
              tmp = tmp0.get_channels(0,cimg::min(2U,dim-1));
            } else tmp = get_channels(0,cimg::min(2U,dim-1));
            switch (old_normalization) {
            case 0: visu0 = tmp; break;
            case 3:
              if (cimg::type<T>::is_float()) visu0 = tmp.normalize(0,(T)255);
              else {
                const float m = (float)cimg::type<T>::min(), M = (float)cimg::type<T>::max();
                visu0.assign(tmp.width,tmp.height,1,tmp.dim);
                unsigned char *ptrd = visu0.end();
                cimg_for(tmp,ptrs,nT) *(--ptrd) = (unsigned char)((*ptrs-m)*255.0f/(M-m));
              } break;
            default: visu0 = tmp.normalize(0,255); break;
            }
            visu0.resize(disp);
          }
          visu = visu0;
          if (!color) {
            if (visu.mean()<200) { fgcolor[0] = fgcolor[1] = fgcolor[2] = 255; bgcolor[0] = bgcolor[1] = bgcolor[2] = 0; }
            else { fgcolor[0] = fgcolor[1] = fgcolor[2] = 0; bgcolor[0] = bgcolor[1] = bgcolor[2] = 255; }
          }

          const int d = (depth>1)?depth:0;
          if (phase) switch (coords_type) {
          case 1: {
            const int
              x0 =(int)((X0+0.5f)*disp.width/(width+d)), y0 = (int)((Y0+0.5f)*disp.height/(height+d)),
              x1 =(int)((X1+0.5f)*disp.width/(width+d)), y1 = (int)((Y1+0.5f)*disp.height/(height+d));
            visu.draw_arrow(x0,y0,x1,y1,fgcolor,30.0f,5.0f,1.0f,hatch);
            if (d) {
              const int
                zx0 = (int)((width+Z0+0.5f)*disp.width/(width+d)), zx1 = (int)((width+Z1+0.5f)*disp.width/(width+d)),
                zy0 = (int)((height+Z0+0.5f)*disp.height/(height+d)), zy1 = (int)((height+Z1+0.5f)*disp.height/(height+d));
              visu.draw_arrow(zx0,y0,zx1,y1,fgcolor,30.0f,5.0f,1.0f,hatch).draw_arrow(x0,zy0,x1,zy1,fgcolor,30.0f,5.0f,1.0f,hatch);
            }
          } break;
          case 2: {
            const int
              x0=(X0<X1?X0:X1)*disp.width/(width+d),
              y0=(Y0<Y1?Y0:Y1)*disp.height/(height+d),
              x1=((X0<X1?X1:X0)+1)*disp.width/(width+d)-1,
              y1=((Y0<Y1?Y1:Y0)+1)*disp.height/(height+d)-1;
            visu.draw_rectangle(x0,y0,x1,y1,fgcolor,0.2f).draw_line(x0,y0,x1,y0,fgcolor,1.0f,hatch).
              draw_line(x1,y0,x1,y1,fgcolor,1.0f,hatch).draw_line(x1,y1,x0,y1,fgcolor,1.0f,hatch).
	      draw_line(x0,y1,x0,y0,fgcolor,1.0f,hatch);
            if (d) {
              const int
                zx0=(int)((width+(Z0<Z1?Z0:Z1))*disp.width/(width+d)),
                zy0=(int)((height+(Z0<Z1?Z0:Z1))*disp.height/(height+d)),
                zx1=(int)((width+(Z0<Z1?Z1:Z0)+1)*disp.width/(width+d))-1,
                zy1=(int)((height+(Z0<Z1?Z1:Z0)+1)*disp.height/(height+d))-1;
              visu.draw_rectangle(zx0,y0,zx1,y1,fgcolor,0.2f).draw_line(zx0,y0,zx1,y0,fgcolor,1.0f,hatch).
                draw_line(zx1,y0,zx1,y1,fgcolor,1.0f,hatch).draw_line(zx1,y1,zx0,y1,fgcolor,1.0f,hatch).
		draw_line(zx0,y1,zx0,y0,fgcolor,1.0f,hatch);
              visu.draw_rectangle(x0,zy0,x1,zy1,fgcolor,0.2f).draw_line(x0,zy0,x1,zy0,fgcolor,1.0f,hatch).
                draw_line(x1,zy0,x1,zy1,fgcolor,1.0f,hatch).draw_line(x1,zy1,x0,zy1,fgcolor,1.0f,hatch).
		draw_line(x0,zy1,x0,zy0,fgcolor,1.0f,hatch);
            }
          } break;
          case 3: {
            const int
              x0=X0*disp.width/(width+d),
              y0=Y0*disp.height/(height+d),
              x1=X1*disp.width/(width+d)-1,
              y1=Y1*disp.height/(height+d)-1;
            visu.draw_ellipse(x0,y0,(float)(x1-x0),(float)(y1-y0),1.0f,0.0f,fgcolor,0.2f).
              draw_ellipse(x0,y0,(float)(x1-x0),(float)(y1-y0),1.0f,0.0f,fgcolor,1.0f,hatch);
            if (d) {
              const int
                zx0=(int)((width+Z0)*disp.width/(width+d)),
                zy0=(int)((height+Z0)*disp.height/(height+d)),
                zx1=(int)((width+Z1+1)*disp.width/(width+d))-1,
                zy1=(int)((height+Z1+1)*disp.height/(height+d))-1;
              visu.draw_ellipse(zx0,y0,(float)(zx1-zx0),(float)(y1-y0),1.0f,0.0f,fgcolor,0.2f).
                draw_ellipse(zx0,y0,(float)(zx1-zx0),(float)(y1-y0),1.0f,0.0f,fgcolor,1.0f,hatch).
                draw_ellipse(x0,zy0,(float)(x1-x0),(float)(zy1-zy0),1.0f,0.0f,fgcolor,0.2f).
                draw_ellipse(x0,zy0,(float)(x1-x0),(float)(zy1-zy0),1.0f,0.0f,fgcolor,1.0f,hatch);
            }
          } break;
          }

          if (my<12) text_down = true;
          if (my>=visu.dimy()-11) text_down = false;
          if (!coords_type || !phase) {
            if (X>=0 && Y>=0 && Z>=0 && X<dimx() && Y<dimy() && Z<dimz()) {
              if (depth>1) std::sprintf(text,"Point (%d,%d,%d)={ ",X,Y,Z); else std::sprintf(text,"Point (%d,%d)={ ",X,Y);
              char *ctext = text + cimg::strlen(text), *const ltext = text + 512;
              for (unsigned int k=0; k<dim && ctext<ltext; ++k) {
                std::sprintf(ctext,cimg::type<T>::format(),cimg::type<T>::format((*this)(X,Y,Z,k)));
                ctext = text + cimg::strlen(text);
                *(ctext++) = ' '; *ctext = '\0';
              }
              std::sprintf(text + cimg::strlen(text),"}");
            }
          } else switch (coords_type) {
          case 1: {
            const double dX = (double)(X0 - X1), dY = (double)(Y0 - Y1), dZ = (double)(Z0 - Z1), norm = std::sqrt(dX*dX+dY*dY+dZ*dZ);
            if (depth>1) std::sprintf(text,"Vect (%d,%d,%d)-(%d,%d,%d), norm=%g",X0,Y0,Z0,X1,Y1,Z1,norm);
            else std::sprintf(text,"Vect (%d,%d)-(%d,%d), norm=%g",X0,Y0,X1,Y1,norm);
          } break;
          case 2:
            if (depth>1) std::sprintf(text,"Box (%d,%d,%d)-(%d,%d,%d), Size=(%d,%d,%d)",
                                      X0<X1?X0:X1,Y0<Y1?Y0:Y1,Z0<Z1?Z0:Z1,
                                      X0<X1?X1:X0,Y0<Y1?Y1:Y0,Z0<Z1?Z1:Z0,
                                      1+cimg::abs(X0-X1),1+cimg::abs(Y0-Y1),1+cimg::abs(Z0-Z1));
            else  std::sprintf(text,"Box (%d,%d)-(%d,%d), Size=(%d,%d)",
                               X0<X1?X0:X1,Y0<Y1?Y0:Y1,X0<X1?X1:X0,Y0<Y1?Y1:Y0,1+cimg::abs(X0-X1),1+cimg::abs(Y0-Y1));
            break;
          default:
            if (depth>1) std::sprintf(text,"Ellipse (%d,%d,%d)-(%d,%d,%d), Radii=(%d,%d,%d)",
                                      X0,Y0,Z0,X1,Y1,Z1,1+cimg::abs(X0-X1),1+cimg::abs(Y0-Y1),1+cimg::abs(Z0-Z1));
            else  std::sprintf(text,"Ellipse (%d,%d)-(%d,%d), Radii=(%d,%d)",
                               X0,Y0,X1,Y1,1+cimg::abs(X0-X1),1+cimg::abs(Y0-Y1));

            break;
          }
          if (phase || (mx>=0 && my>=0)) visu.draw_text(text,0,text_down?visu.dimy()-11:0,fgcolor,bgcolor,11,0.7f);
          disp.display(visu).wait(25);
        } else if (!shape_selected) disp.wait();

        if (disp.is_resized) { disp.resize(false); old_is_resized = true; disp.is_resized = false; visu0.assign(); }
      }

      // Return result
      typedef typename cimg::last<T,int>::type restype;
      CImg<restype> res(1,6,1,1,(restype)-1);
      if (XYZ) { XYZ[0] = (unsigned int)X0; XYZ[1] = (unsigned int)Y0; XYZ[2] = (unsigned int)Z0; }
      if (shape_selected) {
        if (coords_type==2) {
          if (X0>X1) cimg::swap(X0,X1);
          if (Y0>Y1) cimg::swap(Y0,Y1);
          if (Z0>Z1) cimg::swap(Z0,Z1);
        }
	if (X1<0 || Y1<0 || Z1<0) X0 = Y0 = Z0 = X1 = Y1 = Z1 = -1;
	switch(coords_type) {
	case 1:
	case 2:  res[3] = (restype)X1; res[4] = (restype)Y1; res[5] = (restype)Z1;
	default: res[0] = (restype)X0; res[1] = (restype)Y0; res[2] = (restype)Z0;
	}
      }
      disp.button = 0;
      disp.events = old_events;
      disp.normalization = old_normalization;
      disp.is_resized = old_is_resized;
      disp.key = key;
      return res;
    }

    //! High-level interface to select features in images
    CImg<typename cimg::last<T,int>::type> get_coordinates(const int coords_type=0,
                                                           unsigned int *const XYZ=0, const unsigned char *const color=0) const {
      unsigned int imgw = width + (depth>1?depth:0), imgh = height + (depth>1?depth:0);
      CImgDisplay::_fitscreen(imgw,imgh);
      CImgDisplay disp(imgw,imgh," ",1,3);
      return get_coordinates(coords_type,disp,XYZ,color);
    }

    CImg<T>& coordinates(const int coords_type=0, unsigned int *const XYZ=0, const unsigned char *const color=0) {
      return get_coordinates(coords_type,XYZ,color).transfer_to(*this);
    }

    //! High-level interface for displaying a 3d object
    template<typename tp, typename tf, typename tc, typename to>
    const CImg<T>& display_object3d(const CImg<tp>& points, const CImgList<tf>& primitives,
                                    const CImgList<tc>& colors, const CImgList<to>& opacities, CImgDisplay& disp,
                                    const bool centering=true,
                                    const int render_static=4, const int render_motion=1,
                                    const bool double_sided=false, const float focale=500.0f,
                                    const float specular_light=0.2f, const float specular_shine=0.1f,
                                    const bool display_axes=true, float *const pose_matrix=0) const {

      // Check input arguments
      if (!points || !primitives || !opacities)
        throw CImgArgumentException("CImg<%s>::display_object3d() : Given points (%u), primitives (%u) or opacities (%u) are empty.",
                                    pixel_type(),points.size()/3,primitives.size,opacities.size);
      if (is_empty())
        return CImg<T>(disp.width,disp.height,1,colors[0].size(),0).
          display_object3d(points,primitives,colors,opacities,disp,centering,
                           render_static,render_motion,double_sided,focale,specular_light,specular_shine,
                           display_axes,pose_matrix);
      if (points.height<3)
        return display_object3d(points.get_resize(-100,3,1,1,0),primitives,colors,opacities,disp,
                                centering,render_static,render_motion,double_sided,focale,specular_light,specular_shine,
                                display_axes,pose_matrix);

      // Init 3D objects and compute object statistics
      CImg<float> pose, rot_mat,
        centered_points = centering?CImg<float>(points.width,3):CImg<float>(),
        rotated_points(points.width,3),
        bbox_points, rotated_bbox_points,
        axes_points, rotated_axes_points;
      CImgList<to> bbox_opacities, axes_opacities;
      CImgList<T> bbox_colors, axes_colors;
      CImgList<tf> bbox_primitives, axes_primitives;
      float dx = 0, dy = 0, dz = 0, ratio = 1;

      T minval = (T)0, maxval = (T)255;
      if (disp.normalization) minval = colors.minmax(maxval);
      const float meanval = (float)mean();
      bool color_model = true;

      if (cimg::abs(meanval-minval)>cimg::abs(meanval-maxval)) color_model = false;
      const CImg<T>
        bgcolor(1,1,1,dim,color_model?minval:maxval),
        fgcolor(1,1,1,dim,color_model?maxval:minval);
      float
        xm, xM = (float)points.get_shared_line(0).maxmin(xm),
        ym, yM = (float)points.get_shared_line(1).maxmin(ym),
        zm, zM = (float)points.get_shared_line(2).maxmin(zm),
        delta = cimg::max(xM-xm,yM-ym,zM-zm);

      if (display_axes) {
        axes_points.assign(7,3);
        rotated_axes_points.assign(7,3);
        axes_opacities.assign(3,1,1,1,1,1.0f);
        axes_colors.assign(3,dim,1,1,1,fgcolor[0]);
        axes_points(0,0) = 0; axes_points(0,1) = 0; axes_points(0,2) = 0;
        axes_points(1,0) = 20; axes_points(1,1) = 0; axes_points(1,2) = 0;
        axes_points(2,0) = 0; axes_points(2,1) = 20; axes_points(2,2) = 0;
        axes_points(3,0) = 0; axes_points(3,1) = 0; axes_points(3,2) = 20;
        axes_points(4,0) = 22; axes_points(4,1) = -6; axes_points(4,2) = 0;
        axes_points(5,0) = -6; axes_points(5,1) = 22; axes_points(5,2) = 0;
        axes_points(6,0) = -6; axes_points(6,1) = -6; axes_points(6,2) = 22;
        axes_primitives.insert(CImg<tf>::vector(0,1));
        axes_primitives.insert(CImg<tf>::vector(0,2));
        axes_primitives.insert(CImg<tf>::vector(0,3));
      }

      // Begin user interaction loop
      CImg<T> visu0(*this), visu;
      bool init = true, clicked = false, redraw = true;
      unsigned int key = 0;
      int x0 = 0, y0 = 0, x1 = 0, y1 = 0;
      const unsigned int old_events = disp.events;
      disp.show().button = disp.key = 0;
      disp.events = 3;

      while (!disp.is_closed && !key) {

        // Init object position and scale if necessary
        if (init) {
          ratio = delta>0?(2.0f*cimg::min(disp.width,disp.height)/(3.0f*delta)):0;
          dx = 0.5f*(xM+xm); dy = 0.5f*(yM+ym); dz = 0.5f*(zM+zm);
          if (centering) {
            cimg_forX(centered_points,l) {
              centered_points(l,0) = (float)((points(l,0)-dx)*ratio);
              centered_points(l,1) = (float)((points(l,1)-dy)*ratio);
              centered_points(l,2) = (float)((points(l,2)-dz)*ratio);
            }
          }

          if (render_static<0 || render_motion<0) {
            bbox_colors.assign(12,dim,1,1,1,fgcolor[0]);
            bbox_primitives.assign(12,1,2);
            bbox_points.assign(8,3);
            rotated_bbox_points.assign(8,3);
            bbox_points(0,0) = xm; bbox_points(0,1) = ym; bbox_points(0,2) = zm;
            bbox_points(1,0) = xM; bbox_points(1,1) = ym; bbox_points(1,2) = zm;
            bbox_points(2,0) = xM; bbox_points(2,1) = yM; bbox_points(2,2) = zm;
            bbox_points(3,0) = xm; bbox_points(3,1) = yM; bbox_points(3,2) = zm;
            bbox_points(4,0) = xm; bbox_points(4,1) = ym; bbox_points(4,2) = zM;
            bbox_points(5,0) = xM; bbox_points(5,1) = ym; bbox_points(5,2) = zM;
            bbox_points(6,0) = xM; bbox_points(6,1) = yM; bbox_points(6,2) = zM;
            bbox_points(7,0) = xm; bbox_points(7,1) = yM; bbox_points(7,2) = zM;
            bbox_primitives[0].fill(0U,1); bbox_primitives[1].fill(1,2); bbox_primitives[2].fill(2,3); bbox_primitives[3].fill(3,0);
            bbox_primitives[4].fill(4,5); bbox_primitives[5].fill(5,6); bbox_primitives[6].fill(6,7); bbox_primitives[7].fill(7,4);
            bbox_primitives[8].fill(0U,4); bbox_primitives[9].fill(1,5); bbox_primitives[10].fill(2,6); bbox_primitives[11].fill(3,7);
            bbox_opacities.assign(bbox_primitives.size,1,1,1,1,1.0f);
          }

          if (!pose) {
            if (pose_matrix) pose = CImg<float>(pose_matrix,4,4,1,1,false);
            else pose = CImg<float>::identity_matrix(4);
          }
          init = false;
          redraw = true;
        }

        // Rotate and Draw 3D object
        if (redraw) {
          const float
            r00 = pose(0,0), r10 = pose(1,0), r20 = pose(2,0), r30 = pose(3,0),
            r01 = pose(0,1), r11 = pose(1,1), r21 = pose(2,1), r31 = pose(3,1),
            r02 = pose(0,2), r12 = pose(1,2), r22 = pose(2,2), r32 = pose(3,2);
          if ((clicked && render_motion>=0) || (!clicked && render_static>=0)) {
            if (centering) cimg_forX(centered_points,l) {
              const float x = centered_points(l,0), y = centered_points(l,1), z = centered_points(l,2);
              rotated_points(l,0) = r00*x + r10*y + r20*z + r30;
              rotated_points(l,1) = r01*x + r11*y + r21*z + r31;
              rotated_points(l,2) = r02*x + r12*y + r22*z + r32;
            } else cimg_forX(points,l) {
              const float x = (float)points(l,0), y = (float)points(l,1), z = (float)points(l,2);
              rotated_points(l,0) = r00*x + r10*y + r20*z + r30;
              rotated_points(l,1) = r01*x + r11*y + r21*z + r31;
              rotated_points(l,2) = r02*x + r12*y + r22*z + r32;
            }
          } else {
            if (!centering) cimg_forX(bbox_points,l) {
              const float x = bbox_points(l,0), y = bbox_points(l,1), z = bbox_points(l,2);
              rotated_bbox_points(l,0) = r00*x + r10*y + r20*z + r30;
              rotated_bbox_points(l,1) = r01*x + r11*y + r21*z + r31;
              rotated_bbox_points(l,2) = r02*x + r12*y + r22*z + r32;
            } else cimg_forX(bbox_points,l) {
              const float x = (bbox_points(l,0)-dx)*ratio, y = (bbox_points(l,1)-dy)*ratio, z = (bbox_points(l,2)-dz)*ratio;
              rotated_bbox_points(l,0) = r00*x + r10*y + r20*z + r30;
              rotated_bbox_points(l,1) = r01*x + r11*y + r21*z + r31;
              rotated_bbox_points(l,2) = r02*x + r12*y + r22*z + r32;
            }
          }

          // Draw object
          visu = visu0;
          if ((clicked && render_motion<0) || (!clicked && render_static<0))
            visu.draw_object3d(visu.width/2.0f, visu.height/2.0f, 0,
                               rotated_bbox_points,bbox_primitives,bbox_colors,bbox_opacities,1,
                               false,focale,visu.dimx()/2.0f,visu.dimy()/2.0f,-5000.0f,specular_light,0.2f);
          else visu.draw_object3d(visu.width/2.0f, visu.height/2.0f, 0,
                                  rotated_points,primitives,colors,opacities,clicked?render_motion:render_static,
                                  double_sided,focale,visu.dimx()/2.0f,visu.dimy()/2.0f,-5000.0f,specular_light,specular_shine);

          // Draw axes
          if (display_axes) {
            const float Xaxes = 25.0f, Yaxes = visu.height-35.0f;
            cimg_forX(axes_points,l) {
              const float x = axes_points(l,0), y = axes_points(l,1), z = axes_points(l,2);
              rotated_axes_points(l,0) = r00*x + r10*y + r20*z;
              rotated_axes_points(l,1) = r01*x + r11*y + r21*z;
              rotated_axes_points(l,2) = r02*x + r12*y + r22*z;
            }
            axes_opacities(0,0) = (rotated_axes_points(1,2)>0)?0.5f:1.0f;
            axes_opacities(1,0) = (rotated_axes_points(2,2)>0)?0.5f:1.0f;
            axes_opacities(2,0) = (rotated_axes_points(3,2)>0)?0.5f:1.0f;
            visu.draw_object3d(Xaxes,Yaxes,0,rotated_axes_points,axes_primitives,axes_colors,axes_opacities,1,false,focale,0,0,0,0).
              draw_text("X",(int)(Xaxes+rotated_axes_points(4,0)), (int)(Yaxes+rotated_axes_points(4,1)), axes_colors[0].data, (T*)0, 11, axes_opacities(0,0)).
              draw_text("Y",(int)(Xaxes+rotated_axes_points(5,0)), (int)(Yaxes+rotated_axes_points(5,1)), axes_colors[1].data, (T*)0, 11, axes_opacities(1,0)).
              draw_text("Z",(int)(Xaxes+rotated_axes_points(6,0)), (int)(Yaxes+rotated_axes_points(6,1)), axes_colors[2].data, (T*)0, 11, axes_opacities(2,0));
          }
          visu.display(disp);
          if (!clicked || render_motion==render_static) redraw = false;
        }

        // Handle user interaction
        disp.wait();
        if ((disp.button || disp.wheel) && disp.mouse_x>=0 && disp.mouse_y>=0) {
          redraw = true;
          if (!clicked) { x0 = x1 = disp.mouse_x; y0 = y1 = disp.mouse_y; if (!disp.wheel) clicked = true; }
          else { x1 = disp.mouse_x; y1 = disp.mouse_y; }
          if (disp.button&1) {
            const float
              R = 0.45f*cimg::min(disp.width,disp.height),
              R2 = R*R,
              u0 = (float)(x0-disp.dimx()/2),
              v0 = (float)(y0-disp.dimy()/2),
              u1 = (float)(x1-disp.dimx()/2),
              v1 = (float)(y1-disp.dimy()/2),
              n0 = (float)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.0f,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.0f,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);
            rot_mat = CImg<float>::rotation_matrix(u,v,w,alpha);
            rot_mat *= pose.get_crop(0,0,2,2);
            pose.draw_image(rot_mat,0,0);
            x0=x1; y0=y1;
          }
          if (disp.button&2) { pose(3,2)+=(y1-y0); x0 = x1; y0 = y1; }
          if (disp.wheel) { pose(3,2)-=15*disp.wheel; disp.wheel = 0; }
          if (disp.button&4) { pose(3,0)+=(x1-x0); pose(3,1)+=(y1-y0); x0 = x1; y0 = y1; }
          if ((disp.button&1) && (disp.button&2)) { init = true; disp.button = 0; x0 = x1; y0 = y1; pose = CImg<float>::identity_matrix(4); }
        } else if (clicked) { x0 = x1; y0 = y1; clicked = false; redraw = true; }

        key = disp.key;
        if (key && key!=cimg::keyCTRLLEFT && key!=cimg::keyCTRLRIGHT) {
          if (disp.is_key(cimg::keyCTRLLEFT,true) || disp.is_key(cimg::keyCTRLRIGHT,true)) {
            switch (key) {
            case cimg::keyD: if (disp.is_fullscreen) disp.toggle_fullscreen(); disp.resize(-200,-200); disp.is_resized = true; break;
            case cimg::keyC: if (disp.is_fullscreen) disp.toggle_fullscreen(); disp.resize(-50,-50); disp.is_resized = true; break;
            case cimg::keyR: if (disp.is_fullscreen) disp.toggle_fullscreen(); disp.resize(*this); disp.is_resized = true; break;
            case cimg::keyF: disp.resize(disp.screen_dimx(),disp.screen_dimy()).toggle_fullscreen().is_resized = true; break;
            case cimg::keyS: { // Save snapshot
              static unsigned int snap_number = 0;
              char filename[32] = { 0 };
              std::FILE *file;
              do {
                std::sprintf(filename,"CImg_%.4u.bmp",snap_number++);
                if ((file=std::fopen(filename,"r"))!=0) std::fclose(file);
              } while (file);
              (+visu).draw_text(2,2,fgcolor,bgcolor,11,1.0f,"Saving snapshot...",filename).display(disp);
              visu.save(filename);
              visu.draw_text(2,2,fgcolor,bgcolor,11,1.0f,"Snapshot '%s' saved.",filename).display(disp);
            } break;
            case cimg::keyO: { // Save object as an .OFF file
              static unsigned int snap_number = 0;
              char filename[32] = { 0 };
              std::FILE *file;
              do {
                std::sprintf(filename,"CImg_%.4u.off",snap_number++);
                if ((file=std::fopen(filename,"r"))!=0) std::fclose(file);
              } while (file);
              visu.draw_text(2,2,fgcolor,bgcolor,11,1.0f,"Saving object...",filename).display(disp);
              points.save_off(filename,primitives,colors);
              visu.draw_text(2,2,fgcolor,bgcolor,11,1.0f,"Object '%s' saved.",filename).display(disp);
            } break;
            }
            disp.key = key = 0;
          }
        } else key = 0;
        if (disp.is_resized) { disp.resize(false); visu0 = get_resize(disp,1); redraw = true; }
      }
      if (pose_matrix) std::memcpy(pose_matrix,pose.data,16*sizeof(float));
      disp.events = old_events;
      disp.button = 0;
      return *this;
    }

    //! High-level interface for displaying a 3d object
    template<typename tp, typename tf, typename tc, typename to>
    const CImg<T>& display_object3d(const CImgList<tp>& points, const CImgList<tf>& primitives,
                                    const CImgList<tc>& colors, const CImgList<to>& opacities, CImgDisplay &disp,
                                    const bool centering=true,
                                    const int render_static=4, const int render_motion=1,
                                    const bool double_sided=false, const float focale=500.0f,
                                    const float specular_light=0.2f, const float specular_shine=0.1f,
                                    const bool display_axes=true, float *const pose_matrix=0) const {
      CImg<tp> npoints(points.size,3,1,1,0);
      tp *ptrX = npoints.data, *ptrY = npoints.ptr(0,1), *ptrZ = npoints.ptr(0,2);
      cimg_forX(npoints,l) {
        const CImg<tp>& point = points[l];
        const unsigned int siz = point.size();
        if (!siz)
          throw CImgArgumentException("CImg<%s>::display_object3d() : Given points (size=%u) contains a null element at "
                                      "position %u.",pixel_type(),points.size,l);
        *(ptrZ++) = (siz>2)?point(2):0;
        *(ptrY++) = (siz>1)?point(1):0;
        *(ptrX++) = point(0);
      }
      return display_object3d(npoints,primitives,colors,opacities,disp,centering,
                              render_static,render_motion,double_sided,focale,specular_light,specular_shine,
                              display_axes,pose_matrix);
    }

    //! High-level interface for displaying a 3d object
    template<typename tp, typename tf, typename tc, typename to>
    const CImg<T>& display_object3d(const CImg<tp>& points, const CImgList<tf>& primitives,
                                    const CImgList<tc>& colors, const CImg<to>& opacities, CImgDisplay& disp,
                                    const bool centering=true,
                                    const int render_static=4, const int render_motion=1,
                                    const bool double_sided=false, const float focale=500.0f,
                                    const float specular_light=0.2f, const float specular_shine=0.1f,
                                    const bool display_axes=true, float *const pose_matrix=0) const {
      CImgList<to> nopacities(opacities.size(),1);
      cimglist_for(nopacities,l) nopacities(l,0) = opacities(l);
      return display_object3d(points,primitives,colors,nopacities,disp,centering,
                              render_static,render_motion,double_sided,focale,specular_light,specular_shine,
                              display_axes,pose_matrix);
    }

    //! High-level interface for displaying a 3d object
    template<typename tp, typename tf, typename tc, typename to>
    const CImg<T>& display_object3d(const CImgList<tp>& points, const CImgList<tf>& primitives,
                                    const CImgList<tc>& colors, const CImg<to>& opacities, CImgDisplay& disp,
                                    const bool centering=true,
                                    const int render_static=4, const int render_motion=1,
                                    const bool double_sided=false, const float focale=500.0f,
                                    const float specular_light=0.2f, const float specular_shine=0.1f,
                                    const bool display_axes=true, float *const pose_matrix=0) const {
      CImgList<to> nopacities(opacities.size(),1);
      cimglist_for(nopacities,l) nopacities(l,0) = opacities(l);
      if (!points) throw CImgArgumentException("CImg<%s>::display_object3d() : Given points are empty.",
                                               pixel_type());
      CImg<tp> npoints(points.size,3,1,1,0);
      tp *ptrX = npoints.data, *ptrY = npoints.ptr(0,1), *ptrZ = npoints.ptr(0,2);
      { cimg_forX(npoints,l) {
        const CImg<tp>& point = points[l];
        const unsigned int siz = point.size();
        if (!siz)
          throw CImgArgumentException("CImg<%s>::display_object3d() : Given points (size=%u) contains a null element at "
                                      "position %u.",pixel_type(),points.size,l);
        *(ptrZ++) = (siz>2)?point(2):0;
        *(ptrY++) = (siz>1)?point(1):0;
        *(ptrX++) = point(0);
      }
      }
      return display_object3d(npoints,primitives,colors,nopacities,disp,centering,
                              render_static,render_motion,double_sided,focale,specular_light,specular_shine,
                              display_axes,pose_matrix);
    }

    //! High-level interface for displaying a 3d object
    template<typename tp, typename tf, typename tc, typename to>
    const CImg<T>& display_object3d(const tp& points, const CImgList<tf>& primitives,
                                    const CImgList<tc>& colors, const to& opacities,
                                    const bool centering=true,
                                    const int render_static=4, const int render_motion=1,
                                    const bool double_sided=false, const float focale=500.0f,
                                    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(width,height,0,0);
      return display_object3d(points,primitives,colors,opacities,disp,centering,
                              render_static,render_motion,double_sided,focale,specular_light,specular_shine,
                              display_axes,pose_matrix);
    }

    //! High-level interface for displaying a 3d object
    template<typename tp, typename tf, typename tc>
    const CImg<T>& display_object3d(const tp& points, const CImgList<tf>& primitives,
                                    const CImgList<tc>& colors,
                                    const bool centering=true,
                                    const int render_static=4, const int render_motion=1,
                                    const bool double_sided=false, const float focale=500.0f,
                                    const float specular_light=0.2f, const float specular_shine=0.1f,
                                    const bool display_axes=true, float *const pose_matrix=0,
                                    const float opacity=1.0f) const {
      CImgDisplay disp(width,height," ",0);
      return display_object3d(points,primitives,colors,CImg<float>::vector(opacity),
                              disp,centering,render_static,render_motion,double_sided,
                              focale,specular_light,specular_shine,display_axes,pose_matrix);
    }

    //! High-level interface for displaying a 3d object
    template<typename tp, typename tf, typename tc>
    const CImg<T>& display_object3d(const tp& points, const CImgList<tf>& primitives,
                                    const CImgList<tc>& colors, CImgDisplay &disp,
                                    const bool centering=true,
                                    const int render_static=4, const int render_motion=1,
                                    const bool double_sided=false, const float focale=500.0f,
                                    const float specular_light=0.2f, const float specular_shine=0.1f,
                                    const bool display_axes=true, float *const pose_matrix=0,
                                    const float opacity=1.0f) const {
      return display_object3d(points,primitives,colors,CImg<float>::vector(opacity),
                              disp,centering,render_static,render_motion,double_sided,
                              focale,specular_light,specular_shine,display_axes,pose_matrix);
    }

    //@}
    //----------------------
    //
    //! \name Input-Output
    //@{
    //----------------------

    //! Load an image from a file.
    /**
       \param filename = 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 CRAW file (CImg Raw file).
    **/
    static CImg<T> get_load(const char *const filename) {
      return CImg<T>().load(filename);
    }

    CImg<T>& load(const char *const filename) {
      const char *ext = cimg::filename_split(filename);
#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
      if (!cimg::strcasecmp(ext,"asc"))   return load_ascii(filename);
      if (!cimg::strcasecmp(ext,"dlm") ||
          !cimg::strcasecmp(ext,"txt"))   return load_dlm(filename);
      if (!cimg::strcasecmp(ext,"inr"))   return load_inr(filename);
      if (!cimg::strcasecmp(ext,"hdr") ||
          !cimg::strcasecmp(ext,"nii"))   return load_analyze(filename);
      if (!cimg::strcasecmp(ext,"par") ||
          !cimg::strcasecmp(ext,"rec"))   return load_parrec(filename);
      if (!cimg::strcasecmp(ext,"pan"))   return load_pandore(filename);
      if (!cimg::strcasecmp(ext,"bmp"))   return load_bmp(filename);
      if (!cimg::strcasecmp(ext,"png"))   return load_png(filename);
      if (!cimg::strcasecmp(ext,"tif"))   return load_tiff(filename);
      if (!cimg::strcasecmp(ext,"jpg") ||
          !cimg::strcasecmp(ext,"jpeg"))  return load_jpeg(filename);
      if (!cimg::strcasecmp(ext,"ppm") ||
          !cimg::strcasecmp(ext,"pgm") ||
          !cimg::strcasecmp(ext,"pnm"))   return load_pnm(filename);
      if (!cimg::strcasecmp(ext,"cimg") ||
          ext[0]=='\0')                   return load_cimg(filename);
      if (!cimg::strcasecmp(ext,"dcm") ||
          !cimg::strcasecmp(ext,"dicom")) return load_medcon_external(filename);
      if (!cimg::strcasecmp(ext,"avi") ||
          !cimg::strcasecmp(ext,"mov") ||
          !cimg::strcasecmp(ext,"mpg") ||
          !cimg::strcasecmp(ext,"mpeg") ||
	  !cimg::strcasecmp(ext,"ogg") ||
	  !cimg::strcasecmp(ext,"flv"))  return load_ffmpeg(filename);
      return load_other(filename);
    }

    //! Load an image from an ASCII file.
    static CImg<T> get_load_ascii(std::FILE *const file, const char *const filename=0) {
      return CImg<T>().load_ascii(file,filename);
    }

    CImg<T>& load_ascii(std::FILE *const file, const char *const filename=0) {
      std::FILE *const nfile = file?file:cimg::fopen(filename,"rb");
      char line[256] = { 0 };
      unsigned int off, dx = 0, dy = 1, dz = 1, dv = 1;
      int err;
      err = std::fscanf(nfile,"%*[^0-9]");
      if (err != EOF)
        err = std::fscanf(nfile,"%255[^\n]",line);
      if (err == 1) {
        std::sscanf(line,"%u%*c%u%*c%u%*c%u",&dx,&dy,&dz,&dv);
        err = std::fscanf(nfile,"%*[^0-9.+-]");
      } else
        err = EOF;
      if (err == EOF || !dx || !dy || !dz || !dv) {
        if (!file) cimg::fclose(nfile);
        throw CImgIOException("CImg<%s>::load_ascii() : File '%s' is not a valid .ASC file.\n"
                              "Specified image dimensions are (%u,%u,%u,%u).",
                              pixel_type(),filename?filename:"(FILE*)",dx,dy,dz,dv);
      }
      err = 1;
      assign(dx,dy,dz,dv);
      const unsigned long siz = size();
      double val;
      T *ptr = data;
      for (off=0; off<siz && err==1; ++off) {
        err = std::fscanf(nfile,"%lf%*[^0-9.+-]",&val);
        *(ptr++) = (T)val;
      }
      if (err!=1) cimg::warn("CImg<%s>::load_ascii() : File '%s', only %u/%lu values read.",
                             pixel_type(),filename?filename:"(FILE*)",off-1,siz);
      if (!file) cimg::fclose(nfile);
      return *this;
    }

    //! Load an image from an ASCII file.
    static CImg<T> get_load_ascii(const char *const filename) {
      return CImg<T>().load_ascii(0,filename);
    }

    CImg<T>& load_ascii(const char *const filename) {
      return load_ascii(0,filename);
    }

    //! Load an image from a DLM file.
    static CImg<T> get_load_dlm(std::FILE *const file, const char *const filename=0) {
      return CImg<T>().load_dlm(file,filename);
    }

    CImg<T>& load_dlm(std::FILE *const file, const char *const filename=0) {
      std::FILE *const nfile = file?file:cimg::fopen(filename,"r");
      assign(256,256);
      char c, delimiter[256] = { 0 }, tmp[256];
      unsigned int cdx = 0, dx = 0, dy = 0;
      int oerr = 0, err;
      double val;
      while ((err = std::fscanf(nfile,"%lf%255[^0-9.+-]",&val,delimiter))!=EOF) {
        oerr = err;
        if (err>0) (*this)(cdx++,dy) = (T)val;
        if (cdx>=width) resize(width+256,1,1,1,0);
        c = 0; if (!std::sscanf(delimiter,"%255[^\n]%c",tmp,&c) || c=='\n') {
          dx = cimg::max(cdx,dx);
          ++dy;
          if (dy>=height) resize(width,height+256,1,1,0);
          cdx = 0;
        }
      }
      if (cdx && oerr==1) { dx=cdx; ++dy; }
      if (!dx || !dy) {
        if (!file) cimg::fclose(nfile);
        throw CImgIOException("CImg<%s>::load_dlm() : File '%s' is not a valid DLM file.\n"
                              "Specified image dimensions are (%u,%u).",
                              pixel_type(),filename?filename:"(FILE*)",dx,dy);
      }
      resize(dx,dy,1,1,0);
      if (!file) cimg::fclose(nfile);
      return *this;
    }

    //! Load an image from a DLM file.
    static CImg<T> get_load_dlm(const char *const filename) {
      return CImg<T>().load_dlm(0,filename);
    }

    CImg<T>& load_dlm(const char *const filename) {
      return load_dlm(0,filename);
    }

    //! Load an image from a PNM file.
    static CImg<T> get_load_pnm(std::FILE *const file, const char *const filename=0) {
      return CImg<T>().load_pnm(file, filename);
    }

    CImg<T>& load_pnm(std::FILE *const file, const char *const filename=0) {
      std::FILE *const nfile=file?file:cimg::fopen(filename,"rb");
      unsigned int ppm_type, W, H, colormax=255;
      char item[1024] = { 0 };
      int err, rval, gval, bval;
      const int cimg_iobuffer = 12*1024*1024;
      while ((err=std::fscanf(nfile,"%1023[^\n]",item))!=EOF && (item[0]=='#' || !err)) std::fgetc(nfile);
      if(std::sscanf(item," P%u",&ppm_type)!=1) {
        if (!file) cimg::fclose(nfile);
        throw CImgIOException("CImg<%s>::load_pnm() : File '%s', PNM header 'P?' not found.",
                              pixel_type(),filename?filename:"(FILE*)");
      }
      while ((err=std::fscanf(nfile," %1023[^\n]",item))!=EOF && (item[0]=='#' || !err)) std::fgetc(nfile);
      if ((err=std::sscanf(item," %u %u %u",&W,&H,&colormax))<2) {
        if (!file) cimg::fclose(nfile);
        throw CImgIOException("CImg<%s>::load_pnm() : File '%s', WIDTH and HEIGHT fields are not defined in PNM header.",
                              pixel_type(),filename?filename:"(FILE*)");
      }
      if (err==2) {
        while ((err=std::fscanf(nfile," %1023[^\n]",item))!=EOF && (item[0]=='#' || !err)) std::fgetc(nfile);
        if (std::sscanf(item,"%u",&colormax)!=1)
          cimg::warn("CImg<%s>::load_pnm() : File '%s', COLORMAX field is not defined in PNM header.",
                     pixel_type(),filename?filename:"(FILE*)");
      }
      std::fgetc(nfile);
      assign();

      switch (ppm_type) {
      case 2: { // Grey Ascii
        assign(W,H,1,1);
        T* rdata = data;
        cimg_foroff(*this,off) { if (std::fscanf(nfile,"%d",&rval) == EOF) rval = 0; *(rdata++) = (T)rval; }
      } break;
      case 3: { // Color Ascii
        assign(W,H,1,3);
        T *rdata = ptr(0,0,0,0), *gdata = ptr(0,0,0,1), *bdata = ptr(0,0,0,2);
        cimg_forXY(*this,x,y) {
          if (std::fscanf(nfile,"%d %d %d",&rval,&gval,&bval) == EOF)
	    rval = gval = bval = 0;
          *(rdata++) = (T)rval;
          *(gdata++) = (T)gval;
          *(bdata++) = (T)bval; }
      } break;
      case 5: { // Grey Binary
        if (colormax<256) { // 8 bits
          CImg<unsigned char> raw;
          assign(W,H,1,1);
          T *ptrd = ptr(0,0,0,0);
          for (int toread = (int)size(); toread>0; ) {
            raw.assign(cimg::min(toread,cimg_iobuffer));
            cimg::fread(raw.data,raw.width,nfile);
            toread-=raw.width;
            const unsigned char *ptrs = raw.data;
            for (unsigned int off = raw.width; off; --off) *(ptrd++) = (T)*(ptrs++);
          }
        } else { // 16 bits
          CImg<unsigned short> raw;
          assign(W,H,1,1);
          T *ptrd = ptr(0,0,0,0);
          for (int toread = (int)size(); toread>0; ) {
            raw.assign(cimg::min(toread,cimg_iobuffer/2));
            cimg::fread(raw.data,raw.width,nfile);
            toread-=raw.width;
            const unsigned short *ptrs = raw.data;
            for (unsigned int off = raw.width; off; --off) *(ptrd++) = (T)*(ptrs++);
          }
        }
      } break;
      case 6: { // Color Binary
        if (colormax<256) { // 8 bits
          CImg<unsigned char> raw;
          assign(W,H,1,3);
          T
            *ptr_r = ptr(0,0,0,0),
            *ptr_g = ptr(0,0,0,1),
            *ptr_b = ptr(0,0,0,2);
          for (int toread = (int)size(); toread>0; ) {
            raw.assign(cimg::min(toread,cimg_iobuffer));
            cimg::fread(raw.data,raw.width,nfile);
            toread-=raw.width;
            const unsigned char *ptrs = raw.data;
            for (unsigned int off = raw.width/3; off; --off) {
              *(ptr_r++) = (T)*(ptrs++);
              *(ptr_g++) = (T)*(ptrs++);
              *(ptr_b++) = (T)*(ptrs++);
            }
          }
        } else { // 16 bits
          CImg<unsigned short> raw;
          assign(W,H,1,3);
          T
            *ptr_r = ptr(0,0,0,0),
            *ptr_g = ptr(0,0,0,1),
            *ptr_b = ptr(0,0,0,2);
          for (int toread = (int)size(); toread>0; ) {
            raw.assign(cimg::min(toread,cimg_iobuffer/2));
            cimg::fread(raw.data,raw.width,nfile);
            if (!cimg::endian()) cimg::endian_swap(raw.data,raw.width);
            toread-=raw.width;
            const unsigned short *ptrs = raw.data;
            for (unsigned int off = raw.width/3; off; --off) {
              *(ptr_r++) = (T)*(ptrs++);
              *(ptr_g++) = (T)*(ptrs++);
              *(ptr_b++) = (T)*(ptrs++);
            }
          }
        }
      } break;
      default:
        if (!file) cimg::fclose(nfile);
        throw CImgIOException("CImg<%s>::load_pnm() : File '%s', PPM type 'P%d' not supported.",
                              pixel_type(),filename?filename:"(FILE*)",ppm_type);
      }
      if (!file) cimg::fclose(nfile);
      return *this;
    }

    //! Load an image from a PNM file.
    static CImg<T> get_load_pnm(const char *const filename) {
      return get_load_pnm(0,filename);
    }

    CImg<T>& load_pnm(const char *const filename) {
      return load_pnm(0,filename);
    }

    //! Load an image from a BMP file.
    static CImg<T> get_load_bmp(std::FILE *const file, const char *const filename=0) {
      return CImg<T>().load_bmp(file,filename);
    }

    CImg<T>& load_bmp(std::FILE *const file, const char *const filename=0) {
      std::FILE *const nfile = file?file:cimg::fopen(filename,"rb");
      unsigned char header[64];
      cimg::fread(header,54,nfile);
      if (header[0]!='B' || header[1]!='M') {
        if (!file) cimg::fclose(nfile);
        throw CImgIOException("CImg<%s>::load_bmp() : File '%s' is not a valid BMP file.",
                              pixel_type(),filename?filename:"(FILE*)");
      }
      assign();

      // Read header and pixel buffer
      int
        file_size   = header[0x02] + (header[0x03]<<8) + (header[0x04]<<16) + (header[0x05]<<24),
        offset      = header[0x0A] + (header[0x0B]<<8) + (header[0x0C]<<16) + (header[0x0D]<<24),
        dx          = header[0x12] + (header[0x13]<<8) + (header[0x14]<<16) + (header[0x15]<<24),
        dy          = header[0x16] + (header[0x17]<<8) + (header[0x18]<<16) + (header[0x19]<<24),
        compression = header[0x1E] + (header[0x1F]<<8) + (header[0x20]<<16) + (header[0x21]<<24),
        nb_colors   = header[0x2E] + (header[0x2F]<<8) + (header[0x30]<<16) + (header[0x31]<<24),
        bpp         = header[0x1C] + (header[0x1D]<<8),
        *palette    = 0;
      const int
        dx_bytes   = (bpp==1)?(dx/8+(dx%8?1:0)):((bpp==4)?(dx/2+(dx%2?1:0)):(dx*bpp/8)),
        align      = (4-dx_bytes%4)%4,
        buf_size   = cimg::min(cimg::abs(dy)*(dx_bytes+align),file_size-offset);

      if (bpp<16) { if (!nb_colors) nb_colors=1<<bpp; } else nb_colors = 0;
      if (nb_colors) { palette = new int[nb_colors]; cimg::fread(palette,nb_colors,nfile); }
      const int xoffset = offset-54-4*nb_colors;
      if (xoffset>0) std::fseek(nfile,xoffset,SEEK_CUR);
      unsigned char *buffer = new unsigned char[buf_size], *ptrs = buffer;
      cimg::fread(buffer,buf_size,nfile);
      if (!file) cimg::fclose(nfile);

      // Decompress buffer (if necessary)
      if (compression) {
        delete[] buffer;
        if (file) {
          throw CImgIOException("CImg<%s>::load_bmp() : Not able to read a compressed BMP file using a *FILE input",pixel_type());
        } else return load_other(filename);
      }

      // Read pixel data
      assign(dx,cimg::abs(dy),1,3);
      switch (bpp) {
      case 1: { // Monochrome
        for (int y=height-1; y>=0; --y) {
          unsigned char mask = 0x80, val = 0;
          cimg_forX(*this,x) {
            if (mask==0x80) val = *(ptrs++);
            const unsigned char *col = (unsigned char*)(palette+(val&mask?1:0));
            (*this)(x,y,2) = (T)*(col++);
            (*this)(x,y,1) = (T)*(col++);
            (*this)(x,y,0) = (T)*(col++);
            mask = cimg::ror(mask);
          } ptrs+=align; }
      } break;
      case 4: { // 16 colors
        for (int y=height-1; y>=0; --y) {
          unsigned char mask = 0xF0, val = 0;
          cimg_forX(*this,x) {
            if (mask==0xF0) val = *(ptrs++);
            const unsigned char color = (unsigned char)((mask<16)?(val&mask):((val&mask)>>4));
            unsigned char *col = (unsigned char*)(palette+color);
            (*this)(x,y,2) = (T)*(col++);
            (*this)(x,y,1) = (T)*(col++);
            (*this)(x,y,0) = (T)*(col++);
            mask = cimg::ror(mask,4);
          } ptrs+=align; }
      } break;
      case 8: { //  256 colors
        for (int y=height-1; y>=0; --y) { cimg_forX(*this,x) {
          const unsigned char *col = (unsigned char*)(palette+*(ptrs++));
          (*this)(x,y,2) = (T)*(col++);
          (*this)(x,y,1) = (T)*(col++);
          (*this)(x,y,0) = (T)*(col++);
        } ptrs+=align; }
      } break;
      case 16: { // 16 bits colors
        for (int y=height-1; y>=0; --y) { cimg_forX(*this,x) {
          const unsigned char c1 = *(ptrs++), c2 = *(ptrs++);
          const unsigned short col = (unsigned short)(c1|(c2<<8));
          (*this)(x,y,2) = (T)(col&0x1F);
          (*this)(x,y,1) = (T)((col>>5)&0x1F);
          (*this)(x,y,0) = (T)((col>>10)&0x1F);
        } ptrs+=align; }
      } break;
      case 24: { // 24 bits colors
        for (int y=height-1; y>=0; --y) { cimg_forX(*this,x) {
          (*this)(x,y,2) = (T)*(ptrs++);
          (*this)(x,y,1) = (T)*(ptrs++);
          (*this)(x,y,0) = (T)*(ptrs++);
        } ptrs+=align; }
      } break;
      case 32: { // 32 bits colors
        for (int y=height-1; y>=0; --y) { cimg_forX(*this,x) {
          (*this)(x,y,2) = (T)*(ptrs++);
          (*this)(x,y,1) = (T)*(ptrs++);
          (*this)(x,y,0) = (T)*(ptrs++);
          ++ptrs;
        } ptrs+=align; }
      } break;
      }
      if (palette) delete[] palette;
      delete[] buffer;
      if (dy<0) mirror('y');
      return *this;
    }

    //! Load an image from a BMP file.
    static CImg<T> get_load_bmp(const char *const filename) {
      return CImg<T>().load_bmp(0,filename);
    }

    CImg<T>& load_bmp(const char *const filename) {
      return load_bmp(0,filename);
    }

    //! Load an image from a PNG file.
    // (Note : Most of this function has been written by Eric Fausett)
    static CImg<T> get_load_png(std::FILE *const file, const char *const filename=0) {
      return CImg<T>().load_png(file,filename);
    }

    CImg<T>& load_png(std::FILE *const file, const char *const filename=0) {
#ifndef cimg_use_png
      if (file)
        throw CImgIOException("CImg<%s>::load_png() : File '(FILE*)' cannot be read without using libpng.",pixel_type());
      else return load_other(filename);
#else
      // Open file and check for PNG validity
      std::FILE *const nfile = file?file:cimg::fopen(filename,"rb");
      unsigned char pngCheck[8];
      cimg::fread(pngCheck,8,nfile);
      if (png_sig_cmp(pngCheck,0,8)) {
        if (!file) cimg::fclose(nfile);
        throw CImgIOException("CImg<%s>::load_png() : File '%s' is not a valid PNG file.",
                              pixel_type(),filename?filename:"(FILE*)");
      }

      // Setup PNG structures for read
      png_voidp user_error_ptr = 0;
      png_error_ptr user_error_fn = 0, user_warning_fn = 0;
      png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,user_error_ptr,user_error_fn,user_warning_fn);
      if(!png_ptr){
        if (!file) cimg::fclose(nfile);
        throw CImgIOException("CImg<%s>::load_png() : File '%s', trouble initializing 'png_ptr' data structure.",
                              pixel_type(),filename?filename:"(FILE*)");
      }
      png_infop info_ptr = png_create_info_struct(png_ptr);
      if(!info_ptr){
        if (!file) cimg::fclose(nfile);
        png_destroy_read_struct(&png_ptr, (png_infopp)0, (png_infopp)0);
        throw CImgIOException("CImg<%s>::load_png() : File '%s', trouble initializing 'info_ptr' data structure.",
                              pixel_type(),filename?filename:"(FILE*)");
      }
      png_infop end_info = png_create_info_struct(png_ptr);
      if(!end_info){
        if (!file) cimg::fclose(nfile);
        png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)0);
        throw CImgIOException("CImg<%s>::load_png() : File '%s', trouble initializing 'end_info' data structure.",
                              pixel_type(),filename?filename:"(FILE*)");
      }

      // Error handling callback for png file reading
      if (setjmp(png_jmpbuf(png_ptr))){
        if (!file) cimg::fclose(nfile);
        png_destroy_read_struct(&png_ptr, &end_info, (png_infopp)0);
        throw CImgIOException("CImg<%s>::load_png() : File '%s', unknown fatal error.",
                              pixel_type(),filename?filename:"(FILE*)");
      }
      png_init_io(png_ptr, nfile);
      png_set_sig_bytes(png_ptr, 8);

      // Get PNG Header Info up to data block
      png_read_info(png_ptr,info_ptr);
      png_uint_32 W, H;
      int bit_depth, color_type, interlace_type;
      png_get_IHDR(png_ptr,info_ptr,&W,&H,&bit_depth,&color_type,&interlace_type,NULL,NULL);
      int new_bit_depth = bit_depth;
      int new_color_type = color_type;

      // Transforms to unify image data
      if (new_color_type == PNG_COLOR_TYPE_PALETTE){
        png_set_palette_to_rgb(png_ptr);
        new_color_type -= PNG_COLOR_MASK_PALETTE;
        new_bit_depth = 8;
      }
      if (new_color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8){
        png_set_expand_gray_1_2_4_to_8(png_ptr);
        new_bit_depth = 8;
      }
      if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS))
        png_set_tRNS_to_alpha(png_ptr);
      if (new_color_type == PNG_COLOR_TYPE_GRAY || new_color_type == PNG_COLOR_TYPE_GRAY_ALPHA){
        png_set_gray_to_rgb(png_ptr);
        new_color_type |= PNG_COLOR_MASK_COLOR;
      }
      if (new_color_type == PNG_COLOR_TYPE_RGB)
        png_set_filler(png_ptr, 0xffffU, PNG_FILLER_AFTER);
      png_read_update_info(png_ptr,info_ptr);
      if (!(new_bit_depth==8 || new_bit_depth==16)) {
        if (!file) cimg::fclose(nfile);
        png_destroy_read_struct(&png_ptr, &end_info, (png_infopp)0);
        throw CImgIOException("CImg<%s>::load_png() : File '%s', wrong bit coding (bit_depth=%u)",
                              pixel_type(),filename?filename:"(FILE*)",new_bit_depth);
      }
      const int byte_depth = new_bit_depth>>3;

      // Allocate Memory for Image Read
      png_bytep *imgData = new png_bytep[H];
      for (unsigned int row = 0; row<H; ++row) imgData[row] = new png_byte[byte_depth*4*W];
      png_read_image(png_ptr,imgData);
      png_read_end(png_ptr,end_info);

      // Read pixel data
      if (!(new_color_type==PNG_COLOR_TYPE_RGB || new_color_type==PNG_COLOR_TYPE_RGB_ALPHA)) {
        if (!file) cimg::fclose(nfile);
        png_destroy_read_struct(&png_ptr,&end_info,(png_infopp)0);
        throw CImgIOException("CImg<%s>::load_png() : File '%s', wrong color coding (new_color_type=%u)",
                              pixel_type(),filename?filename:"(FILE*)",new_color_type);
      }
      const bool no_alpha_channel = (new_color_type==PNG_COLOR_TYPE_RGB);
      assign(W,H,1,no_alpha_channel?3:4);
      T *ptr1 = ptr(0,0,0,0), *ptr2 = ptr(0,0,0,1), *ptr3 = ptr(0,0,0,2), *ptr4 = ptr(0,0,0,3);
      switch(new_bit_depth) {
      case 8: {
        cimg_forY(*this,y){
          const unsigned char *ptrs = (unsigned char*)imgData[y];
          cimg_forX(*this,x){
            *(ptr1++) = (T)*(ptrs++);
            *(ptr2++) = (T)*(ptrs++);
            *(ptr3++) = (T)*(ptrs++);
            if (no_alpha_channel) ++ptrs; else *(ptr4++) = (T)*(ptrs++);
          }
        }
      } break;
      case 16: {
        cimg_forY(*this,y){
          const unsigned short *ptrs = (unsigned short*)(imgData[y]);
          if (!cimg::endian()) cimg::endian_swap(ptrs,4*width);
          cimg_forX(*this,x){
            *(ptr1++) = (T)*(ptrs++);
            *(ptr2++) = (T)*(ptrs++);
            *(ptr3++) = (T)*(ptrs++);
            if (no_alpha_channel) ++ptrs; else *(ptr4++) = (T)*(ptrs++);
          }
        }
      } break;
      }
      png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);

      // Deallocate Image Read Memory
      cimg_forY(*this,n) delete[] imgData[n];
      delete[] imgData;
      if (!file) cimg::fclose(nfile);
      return *this;
#endif
    }

    //! Load an image from a PNG file.
    static CImg<T> get_load_png(const char *const filename) {
      return CImg<T>().load_png(0,filename);
    }

    CImg<T>& load_png(const char *const filename) {
      return load_png(0,filename);
    }

    // Read a 2D sub-image from a tiff file.
    // (Original contribution by Jerome Boulanger).
#ifdef cimg_use_tiff
    CImg<T>& _load_tiff(TIFF *tif, const unsigned int directory) {
      if (!TIFFSetDirectory(tif,directory)) return assign();
      uint16 samplesperpixel, bitspersample;
      uint32 nx,ny;
      const char *const filename = TIFFFileName(tif);
      TIFFGetField(tif,TIFFTAG_IMAGEWIDTH,&nx);
      TIFFGetField(tif,TIFFTAG_IMAGELENGTH,&ny);
      TIFFGetField(tif,TIFFTAG_SAMPLESPERPIXEL,&samplesperpixel);
      if (samplesperpixel!=1 && samplesperpixel!=3 && samplesperpixel!=4) {
	cimg::warn("CImg<%s>::load_tiff() : File '%s', unknown value for tag : TIFFTAG_SAMPLESPERPIXEL, will force it to 1.",
                   pixel_type(),filename);
	samplesperpixel = 1;
      }
      TIFFGetFieldDefaulted(tif,TIFFTAG_BITSPERSAMPLE,&bitspersample);
      assign(nx,ny,1,samplesperpixel);
      if (bitspersample!=8 || !(samplesperpixel==3 || samplesperpixel==4)) {
	uint16 photo, config;
	TIFFGetField(tif,TIFFTAG_PLANARCONFIG,&config);
	TIFFGetField(tif,TIFFTAG_PHOTOMETRIC,&photo);
	if (TIFFIsTiled(tif)) {
	  uint32 tw, th;
	  TIFFGetField(tif,TIFFTAG_TILEWIDTH,&tw);
	  TIFFGetField(tif,TIFFTAG_TILELENGTH,&th);
	  if (config==PLANARCONFIG_CONTIG) switch(bitspersample) {
          case 8: {
	      unsigned char *buf = (unsigned char*)_TIFFmalloc(TIFFTileSize(tif));
	      if (buf) {
		for (unsigned int row = 0; row<ny; row+=th)
		  for (unsigned int col = 0; col<nx; col+=tw) {
		    if (TIFFReadTile(tif,buf,col,row,0,0)<0) {
		      _TIFFfree(buf); TIFFClose(tif);
		      throw CImgException("CImg<%s>::load_tiff() : File '%s', an error occure while reading a tile.",
					  pixel_type(),filename);
		    } else {
		      unsigned char *ptr = buf;
		      for (unsigned int rr = row; rr<cimg::min((unsigned int)(row+th),(unsigned int)ny); ++rr)
			for (unsigned int cc = col; cc<cimg::min((unsigned int)(col+tw),(unsigned int)nx); ++cc)
			  for (unsigned int vv = 0; vv<samplesperpixel; ++vv)
			    (*this)(cc,rr,vv) = (T)(float)(ptr[(rr-row)*th*samplesperpixel + (cc-col)*samplesperpixel + vv]);
		    }
		  }
		_TIFFfree(buf);
	      }
	    } break;
	    case 16: {
	      unsigned short *buf = (unsigned short*)_TIFFmalloc(TIFFTileSize(tif));
	      if (buf) {
		for (unsigned int row = 0; row<ny; row+=th)
		  for (unsigned int col = 0; col<nx; col+=tw) {
		    if (TIFFReadTile(tif,buf,col,row,0,0)<0) {
		      _TIFFfree(buf); TIFFClose(tif);
		      throw CImgException("CImg<%s>::load_tiff() : File '%s', an error occure while reading a tile.",
					  pixel_type(),filename);
		    } else {
		      unsigned short *ptr = buf;
		      for (unsigned int rr = row; rr<cimg::min((unsigned int)(row+th),(unsigned int)ny); ++rr)
			for (unsigned int cc = col; cc<cimg::min((unsigned int)(col+tw),(unsigned int)nx); ++cc)
			  for (unsigned int vv = 0; vv<samplesperpixel; ++vv)
			    (*this)(cc,rr,vv) = (T)(float)(ptr[(rr-row)*th*samplesperpixel + (cc-col)*samplesperpixel + vv]);
		    }
		  }
		_TIFFfree(buf);
	      }
	    } break;
	    case 32: {
	      float *buf = (float*)_TIFFmalloc(TIFFTileSize(tif));
	      if (buf) {
		for (unsigned int row = 0; row<ny; row+=th)
		  for (unsigned int col = 0; col<nx; col+=tw) {
		    if (TIFFReadTile(tif,buf,col,row,0,0)<0) {
		      _TIFFfree(buf); TIFFClose(tif);
		      throw CImgException("CImg<%s>::load_tiff() : File '%s', an error occure while reading a tile.",
					  pixel_type(),filename);
		    } else {
		      float *ptr = buf;
		      for (unsigned int rr = row; rr<cimg::min((unsigned int)(row+th),(unsigned int)ny); ++rr)
			for (unsigned int cc = col; cc<cimg::min((unsigned int)(col+tw),(unsigned int)nx); ++cc)
			  for (unsigned int vv = 0; vv<samplesperpixel; ++vv)
			    (*this)(cc,rr,vv) = (T)(float)(ptr[(rr-row)*th*samplesperpixel + (cc-col)*samplesperpixel + vv]);
		    }
		  }
		_TIFFfree(buf);
	      }
	    } break;
	    } else switch (bitspersample) {
	    case 8: {
	      unsigned char *buf = (unsigned char*)_TIFFmalloc(TIFFTileSize(tif));
	      if (buf) {
		for (unsigned int vv = 0; vv<samplesperpixel; ++vv)
		  for (unsigned int row = 0; row<ny; row+=th)
		    for (unsigned int col = 0; col<nx; col+=tw) {
		      if (TIFFReadTile(tif,buf,col,row,0,vv)<0) {
			_TIFFfree(buf); TIFFClose(tif);
			throw CImgException("CImg<%s>::load_tiff() : File '%s', an error occure while reading a tile.",
					    pixel_type(),filename);
		      } else {
			unsigned char *ptr = buf;
			for (unsigned int rr = row; rr<cimg::min((unsigned int)(row+th),(unsigned int)ny); ++rr)
			  for (unsigned int cc = col; cc<cimg::min((unsigned int)(col+tw),(unsigned int)nx); ++cc)
			    (*this)(cc,rr,vv) = (T)(float)*(ptr++);
		      }
		    }
		_TIFFfree(buf);
	      }
	    } break;
	    case 16: {
	      unsigned short *buf = (unsigned short*)_TIFFmalloc(TIFFTileSize(tif));
	      if (buf) {
		for (unsigned int vv = 0; vv<samplesperpixel; ++vv)
		  for (unsigned int row = 0; row<ny; row+=th)
		    for (unsigned int col = 0; col<nx; col+=tw) {
		      if (TIFFReadTile(tif,buf,col,row,0,vv)<0) {
			_TIFFfree(buf); TIFFClose(tif);
			throw CImgException("CImg<%s>::load_tiff() : File '%s', an error occure while reading a tile.",
					    pixel_type(),filename);
		      } else {
			unsigned short *ptr = buf;
			for (unsigned int rr = row; rr<cimg::min((unsigned int)(row+th),(unsigned int)ny); ++rr)
			  for (unsigned int cc = col; cc<cimg::min((unsigned int)(col+tw),(unsigned int)nx); ++cc)
			    (*this)(cc,rr,vv) = (T)(float)*(ptr++);
		      }
		    }
		_TIFFfree(buf);
	      }
	    } break;
	    case 32: {
	      float *buf = (float*)_TIFFmalloc(TIFFTileSize(tif));
	      if (buf) {
		for (unsigned int vv = 0; vv<samplesperpixel; ++vv)
		  for (unsigned int row = 0; row<ny; row+=th)
		    for (unsigned int col = 0; col<nx; col+=tw) {
		      if (TIFFReadTile(tif,buf,col,row,0,vv)<0) {
			_TIFFfree(buf); TIFFClose(tif);
			throw CImgException("CImg<%s>::load_tiff() : File '%s', an error occure while reading a tile.",
					    pixel_type(),filename);
		      } else {
			float *ptr = buf;
			for (unsigned int rr = row; rr<cimg::min((unsigned int)(row+th),(unsigned int)ny); ++rr)
			  for (unsigned int cc = col; cc<cimg::min((unsigned int)(col+tw),(unsigned int)nx); ++cc)
			    (*this)(cc,rr,vv) = (T)(float)*(ptr++);
		      }
		    }
		_TIFFfree(buf);
	      }
	    } break;
	    }
	} else {
	  if (config==PLANARCONFIG_CONTIG) switch (bitspersample) {
	    case 8: {
	      unsigned char *buf = (unsigned char*)_TIFFmalloc(TIFFStripSize(tif));
	      if (buf) {
		uint32 row, rowsperstrip = (uint32)-1;
		TIFFGetField(tif,TIFFTAG_ROWSPERSTRIP,&rowsperstrip);
		for (row = 0; row<ny; row+= rowsperstrip) {
		  uint32 nrow = (row+rowsperstrip>ny?ny-row:rowsperstrip);
		  tstrip_t strip = TIFFComputeStrip(tif, row, 0);
		  if ((TIFFReadEncodedStrip(tif,strip,buf,-1))<0) {
		    _TIFFfree(buf); TIFFClose(tif);
		    throw CImgException("CImg<%s>::load_tiff() : File '%s', an error occure while reading a strip.",
					pixel_type(),filename);
		  }
		  unsigned char *ptr = buf;
		  for (unsigned int rr = 0; rr<nrow; ++rr)
		    for (unsigned int cc = 0; cc<nx; ++cc)
		      for (unsigned int vv = 0; vv<samplesperpixel; ++vv) (*this)(cc,row+rr,vv) = (T)(float)*(ptr++);
		}
		_TIFFfree(buf);
	      }
	    } break;
	    case 16: {
	      unsigned short *buf = (unsigned short*)_TIFFmalloc(TIFFStripSize(tif));
	      if (buf) {
		uint32 row, rowsperstrip = (uint32)-1;
		TIFFGetField(tif,TIFFTAG_ROWSPERSTRIP,&rowsperstrip);
		for (row = 0; row<ny; row+= rowsperstrip) {
		  uint32 nrow = (row+rowsperstrip>ny?ny-row:rowsperstrip);
		  tstrip_t strip = TIFFComputeStrip(tif, row, 0);
		  if ((TIFFReadEncodedStrip(tif,strip,buf,-1))<0) {
		    _TIFFfree(buf); TIFFClose(tif);
		    throw CImgException("CImg<%s>::load_tiff() : File '%s', error while reading a strip.",
					pixel_type(),filename);
		  }
		  unsigned short *ptr = buf;
		  for (unsigned int rr = 0; rr<nrow; ++rr)
		    for (unsigned int cc = 0; cc<nx; ++cc)
		      for (unsigned int vv = 0; vv<samplesperpixel; ++vv) (*this)(cc,row+rr,vv) = (T)(float)*(ptr++);
		}
		_TIFFfree(buf);
	      }
	    } break;
	    case 32: {
	      float *buf = (float*)_TIFFmalloc(TIFFStripSize(tif));
	      if (buf) {
		uint32 row, rowsperstrip = (uint32)-1;
		TIFFGetField(tif,TIFFTAG_ROWSPERSTRIP,&rowsperstrip);
		for (row = 0; row<ny; row+= rowsperstrip) {
		  uint32 nrow = (row+rowsperstrip>ny?ny-row:rowsperstrip);
		  tstrip_t strip = TIFFComputeStrip(tif, row, 0);
		  if ((TIFFReadEncodedStrip(tif,strip,buf,-1))<0) {
		    _TIFFfree(buf); TIFFClose(tif);
		    throw CImgException("CImg<%s>::load_tiff() : File '%s', error while reading a strip.",
					pixel_type(),filename);
		  }
		  float *ptr = buf;
		  for (unsigned int rr = 0; rr<nrow; ++rr)
		    for (unsigned int cc = 0; cc<nx; ++cc)
		      for (unsigned int vv = 0; vv<samplesperpixel; ++vv) (*this)(cc,row+rr,vv) = (T)(float)*(ptr++);
		}
		_TIFFfree(buf);
	      }
	    } break;
	    } else switch(bitspersample){
	    case 8: {
	      unsigned char *buf = (unsigned char*)_TIFFmalloc(TIFFStripSize(tif));
	      if (buf) {
		uint32 row, rowsperstrip = (uint32)-1;
		TIFFGetField(tif,TIFFTAG_ROWSPERSTRIP,&rowsperstrip);
		for (unsigned int vv=0; vv<samplesperpixel; ++vv)
		  for (row = 0; row<ny; row+= rowsperstrip) {
		    uint32 nrow = (row+rowsperstrip>ny?ny-row:rowsperstrip);
		    tstrip_t strip = TIFFComputeStrip(tif, row, vv);
		    if ((TIFFReadEncodedStrip(tif,strip,buf,-1))<0) {
		      _TIFFfree(buf); TIFFClose(tif);
		      throw CImgException("CImg<%s>::load_tiff() : File '%s', an error occure while reading a strip.",
					  pixel_type(),filename);
		    }
		    unsigned char *ptr = buf;
		    for (unsigned int rr = 0;rr<nrow; ++rr)
		      for (unsigned int cc = 0; cc<nx; ++cc)
			(*this)(cc,row+rr,vv) = (T)(float)*(ptr++);
		  }
		_TIFFfree(buf);
	      }
	    } break;
	    case 16: {
	      unsigned short *buf = (unsigned short*)_TIFFmalloc(TIFFStripSize(tif));
	      if (buf) {
		uint32 row, rowsperstrip = (uint32)-1;
		TIFFGetField(tif,TIFFTAG_ROWSPERSTRIP,&rowsperstrip);
		for (unsigned int vv = 0; vv<samplesperpixel; ++vv)
		  for (row = 0; row<ny; row+= rowsperstrip) {
		    uint32 nrow = (row+rowsperstrip>ny?ny-row:rowsperstrip);
		    tstrip_t strip = TIFFComputeStrip(tif, row, vv);
		    if ((TIFFReadEncodedStrip(tif,strip,buf,-1))<0) {
		      _TIFFfree(buf); TIFFClose(tif);
		      throw CImgException("CImg<%s>::load_tiff() : File '%s', error while reading a strip.",
					  pixel_type(),filename);
		    }
		    unsigned short *ptr = buf;
		    for (unsigned int rr = 0; rr<nrow; ++rr)
		      for (unsigned int cc = 0; cc<nx; ++cc)
			(*this)(cc,row+rr,vv) = (T)(float)*(ptr++);
		  }
		_TIFFfree(buf);
	      }
	    } break;
	    case 32: {
	      float *buf = (float*)_TIFFmalloc(TIFFStripSize(tif));
	      if (buf) {
		uint32 row, rowsperstrip = (uint32)-1;
		TIFFGetField(tif,TIFFTAG_ROWSPERSTRIP,&rowsperstrip);
		for (unsigned int vv = 0; vv<samplesperpixel; ++vv)
		  for (row = 0; row<ny; row+= rowsperstrip) {
		    uint32 nrow = (row+rowsperstrip>ny?ny-row:rowsperstrip);
		    tstrip_t strip = TIFFComputeStrip(tif, row, vv);
		    if ((TIFFReadEncodedStrip(tif,strip,buf,-1))<0) {
		      _TIFFfree(buf); TIFFClose(tif);
		      throw CImgException("CImg<%s>::load_tiff() : File '%s', error while reading a strip.",
					  pixel_type(),filename);
		    }
		    float *ptr = buf;
		    for (unsigned int rr = 0; rr<nrow; ++rr)  for (unsigned int cc = 0; cc<nx; ++cc)
			(*this)(cc,row+rr,vv) = (T)(float)*(ptr++);
		  }
		_TIFFfree(buf);
	      }
	    } break;
	    }
	}
      } else {
	uint32* raster = (uint32*)_TIFFmalloc(nx * ny * sizeof (uint32));
	if (!raster) {
	  _TIFFfree(raster); TIFFClose(tif);
	  throw CImgException("CImg<%s>::load_tiff() : File '%s', not enough memory for buffer allocation.",
			      pixel_type(),filename);
	}
	TIFFReadRGBAImage(tif,nx,ny,raster,0);
	switch (samplesperpixel) {
	case 1: {
	  cimg_forXY(*this,x,y) (*this)(x,y) = (T)(float)((raster[nx*(ny-1-y)+x]+ 128) / 257);
	} break;
	case 3: {
	  cimg_forXY(*this,x,y) {
	    (*this)(x,y,0) = (T)(float)TIFFGetR(raster[nx*(ny-1-y)+x]);
	    (*this)(x,y,1) = (T)(float)TIFFGetG(raster[nx*(ny-1-y)+x]);
	    (*this)(x,y,2) = (T)(float)TIFFGetB(raster[nx*(ny-1-y)+x]);
	  }
	} break;
	case 4: {
	  cimg_forXY(*this,x,y) {
	    (*this)(x,y,0) = (T)(float)TIFFGetR(raster[nx*(ny-1-y)+x]);
	    (*this)(x,y,1) = (T)(float)TIFFGetG(raster[nx*(ny-1-y)+x]);
	    (*this)(x,y,2) = (T)(float)TIFFGetB(raster[nx*(ny-1-y)+x]);
	    (*this)(x,y,3) = (T)(float)TIFFGetA(raster[nx*(ny-1-y)+x]);
	  }
	} break;
	}
	_TIFFfree(raster);
      }
      return *this;
    }
#endif

    //! Load an image from a TIFF file.
    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);
    }

    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) {
      const unsigned int
	nfirst_frame = first_frame<last_frame?first_frame:last_frame,
	nstep_frame = step_frame?step_frame:1;
      unsigned int nlast_frame = first_frame<last_frame?last_frame:first_frame;

#ifndef cimg_use_tiff
      if (nfirst_frame || nlast_frame!=~0U || nstep_frame>1)
        throw CImgArgumentException("CImg<%s>::load_tiff() : File '%s', reading sub-images from a tiff file requires the use of libtiff.\n"
                                    "('cimg_use_tiff' must be defined).",pixel_type(),filename);
      return load_other(filename);
#else
      TIFF *tif = TIFFOpen(filename,"r");
      if (tif) {
        unsigned int nb_images = 0;
        do ++nb_images; while (TIFFReadDirectory(tif));
        if (nfirst_frame>=nb_images || (nlast_frame!=~0U && nlast_frame>=nb_images))
          cimg::warn("CImg<%s>::load_tiff() : File '%s' contains %u image(s), specified frame range is [%u,%u] (step %u).",
                     pixel_type(),filename,nb_images,nfirst_frame,nlast_frame,nstep_frame);
        if (nfirst_frame>=nb_images) return assign();
        if (nlast_frame>=nb_images) nlast_frame = nb_images-1;
        TIFFSetDirectory(tif,0);
        CImg<T> frame;
        for (unsigned int l = nfirst_frame; l<=nlast_frame; l+=nstep_frame) {
          frame._load_tiff(tif,l);
          if (l==nfirst_frame) assign(frame.width,frame.height,1+(nlast_frame-nfirst_frame)/nstep_frame,frame.dim);
          if (frame.width>width || frame.height>height || frame.dim>dim)
            resize(cimg::max(frame.width,width),cimg::max(frame.height,height),-100,cimg::max(frame.dim,dim),0);
          draw_image(frame,0,0,(l-nfirst_frame)/nstep_frame,0);
        }
        TIFFClose(tif);
      } else throw CImgException("CImg<%s>::load_tiff() : File '%s', cannot open file.",pixel_type(),filename);
      return *this;
#endif
    }

    //! Load an image from a JPEG file.
    static CImg<T> get_load_jpeg(std::FILE *const file, const char *const filename=0) {
      return CImg<T>().load_jpeg(file,filename);
    }

    CImg<T>& load_jpeg(std::FILE *const file, const char *const filename=0) {
#ifndef cimg_use_jpeg
      if (file)
        throw CImgIOException("CImg<%s>::load_jpeg() : File '(FILE*)' cannot be read without using libjpeg.",
                              pixel_type());
      else return load_other(filename);
#else
      struct jpeg_decompress_struct cinfo;
      struct jpeg_error_mgr jerr;
      std::FILE *const nfile = file?file:cimg::fopen(filename,"rb");

      cinfo.err = jpeg_std_error(&jerr);
      jpeg_create_decompress(&cinfo);
      jpeg_stdio_src(&cinfo,nfile);
      jpeg_read_header(&cinfo,TRUE);
      jpeg_start_decompress(&cinfo);

      if (cinfo.output_components!=1 && cinfo.output_components!=3 && cinfo.output_components!=4) {
        cimg::warn("CImg<%s>::load_jpeg() : Don't know how to read image '%s' with libpeg, trying ImageMagick's convert",
                   pixel_type(),filename?filename:"(unknown)");
        if (!file) return load_other(filename);
        else {
          if (!file) cimg::fclose(nfile);
          throw CImgIOException("CImg<%s>::load_jpeg() : Cannot read JPEG image '%s' using a *FILE input.",
                                pixel_type(),filename?filename:"(FILE*)");
        }
      }

      const unsigned int row_stride = cinfo.output_width * cinfo.output_components;
      unsigned char *buf = new unsigned char[cinfo.output_width*cinfo.output_height*cinfo.output_components], *buf2 = buf;
      JSAMPROW row_pointer[1];
      while (cinfo.output_scanline < cinfo.output_height) {
        row_pointer[0] = &buf[cinfo.output_scanline*row_stride];
        jpeg_read_scanlines(&cinfo,row_pointer,1);
      }
      jpeg_finish_decompress(&cinfo);
      jpeg_destroy_decompress(&cinfo);
      if (!file) cimg::fclose(nfile);

      assign(cinfo.output_width,cinfo.output_height,1,cinfo.output_components);
      switch (dim) {
      case 1: {
        T *ptr_g = data;
        cimg_forXY(*this,x,y) *(ptr_g++) = (T)*(buf2++);
      } break;
      case 3: {
        T *ptr_r = ptr(0,0,0,0), *ptr_g = ptr(0,0,0,1), *ptr_b = ptr(0,0,0,2);
        cimg_forXY(*this,x,y) {
          *(ptr_r++) = (T)*(buf2++);
          *(ptr_g++) = (T)*(buf2++);
          *(ptr_b++) = (T)*(buf2++);
        }
      } break;
      case 4: {
        T *ptr_r = ptr(0,0,0,0), *ptr_g = ptr(0,0,0,1),
          *ptr_b = ptr(0,0,0,2), *ptr_a = ptr(0,0,0,3);
        cimg_forXY(*this,x,y) {
          *(ptr_r++) = (T)*(buf2++);
          *(ptr_g++) = (T)*(buf2++);
          *(ptr_b++) = (T)*(buf2++);
          *(ptr_a++) = (T)*(buf2++);
        }
      } break;
      }
      delete[] buf;
      return *this;
#endif
    }

    //! Load an image from a JPEG file.
    static CImg<T> get_load_jpeg(const char *const filename) {
      return CImg<T>().load_jpeg(0,filename);
    }

    CImg<T>& load_jpeg(const char *const filename) {
      return load_jpeg(0,filename);
    }

    //! Load an image using builtin ImageMagick++ Library.
    /**
       Added April/may 2006 by Christoph Hormann <chris_hormann@gmx.de>
       This is experimental code, not much tested, use with care.
    **/
    static CImg<T> get_load_magick(const char *const filename) {
      return CImg<T>().load_magick(filename);
    }

    CImg<T>& load_magick(const char *const filename) {
#ifdef cimg_use_magick
      Magick::Image image(filename);
      const unsigned int W = image.size().width(), H = image.size().height();
      switch (image.type()) {
      case Magick::PaletteMatteType:
      case Magick::TrueColorMatteType:
      case Magick::ColorSeparationType: {
        assign(W,H,1,4);
        T *rdata = ptr(0,0,0,0), *gdata = ptr(0,0,0,1), *bdata = ptr(0,0,0,2), *adata = ptr(0,0,0,3);
        Magick::PixelPacket *pixels = image.getPixels(0,0,W,H);
        for (unsigned int off = W*H; off; --off) {
          *(rdata++) = (T)(pixels->red);
          *(gdata++) = (T)(pixels->green);
          *(bdata++) = (T)(pixels->blue);
          *(adata++) = (T)(pixels->opacity);
          ++pixels;
        }
      } break;
      case Magick::PaletteType:
      case Magick::TrueColorType: {
        assign(W,H,1,3);
        T *rdata = ptr(0,0,0,0), *gdata = ptr(0,0,0,1), *bdata = ptr(0,0,0,2);
        Magick::PixelPacket *pixels = image.getPixels(0,0,W,H);
        for (unsigned int off = W*H; off; --off) {
          *(rdata++) = (T)(pixels->red);
          *(gdata++) = (T)(pixels->green);
          *(bdata++) = (T)(pixels->blue);
          ++pixels;
        }
      } break;
      case Magick::GrayscaleMatteType: {
        assign(W,H,1,2);
        T *data = ptr(0,0,0,0), *adata = ptr(0,0,0,1);
        Magick::PixelPacket *pixels = image.getPixels(0,0,W,H);
        for (unsigned int off = W*H; off; --off) {
          *(data++) = (T)(pixels->red);
          *(adata++) = (T)(pixels->opacity);
          ++pixels;
        }
      } break;
      default: {
        assign(W,H,1,1);
        T *data = ptr(0,0,0,0);
        Magick::PixelPacket *pixels = image.getPixels(0,0,W,H);
        for (unsigned int off = W*H; off; --off) {
          *(data++) = (T)(pixels->red);
          ++pixels;
        }
      } break;
      }
#else
      throw CImgIOException("CImg<%s>::load_magick() : File '%s', Magick++ has not been linked during compilation.",
                            pixel_type(),filename?filename:"(null)");
#endif
      return *this;
    }

    //! Load an image from a .RAW file.
    static CImg<T> get_load_raw(std::FILE *const file, const char *const filename,
                                const unsigned int sizex, const unsigned int sizey=1,
                                const unsigned int sizez=1, const unsigned int sizev=1,
                                const bool multiplexed=false, const bool endian_swap=false) {
      return CImg<T>().load_raw(file,filename,sizex,sizey,sizez,sizev,multiplexed,endian_swap);
    }

    CImg<T>& load_raw(std::FILE *const file, const char *const filename,
                      const unsigned int sizex, const unsigned int sizey=1,
                      const unsigned int sizez=1, const unsigned int sizev=1,
                      const bool multiplexed = false, const bool endian_swap = false) {
      assign(sizex,sizey,sizez,sizev,0);
      const unsigned int siz = size();
      if (siz) {
        std::FILE *const nfile = file?file:cimg::fopen(filename,"rb");
        if (!multiplexed) {
          cimg::fread(data,siz,nfile);
          if (endian_swap) cimg::endian_swap(data,siz);
        }
        else {
          CImg<T> buf(1,1,1,sizev);
          cimg_forXYZ(*this,x,y,z) {
            cimg::fread(buf.data,sizev,nfile);
            if (endian_swap) cimg::endian_swap(buf.data,sizev);
            set_vector_at(buf,x,y,z); }
        }
        if (!file) cimg::fclose(nfile);
      }
      return *this;
    }

    //! Load an image from a .RAW file.
    static CImg<T> get_load_raw(const char *const filename,
                                const unsigned int sizex, const unsigned int sizey=1,
                                const unsigned int sizez=1, const unsigned int sizev=1,
                                const bool multiplexed = false, const bool endian_swap = false) {
      return CImg<T>().load_raw(0,filename,sizex,sizey,sizez,sizev,multiplexed,endian_swap);
    }

    CImg<T>& load_raw(const char *const filename,
                      const unsigned int sizex, const unsigned int sizey=1,
                      const unsigned int sizez=1, const unsigned int sizev=1,
                      const bool multiplexed = false, const bool endian_swap = false) {
      return load_raw(0,filename,sizex,sizey,sizez,sizev,multiplexed,endian_swap);
    }

    //! Load an image from a RGBA file.
    static CImg<T> get_load_rgba(std::FILE *const file, const char *const filename, const unsigned int dimw, const unsigned int dimh) {
      return CImg<T>().load_rgba(file,filename,dimw,dimh);
    }

    //! In-place version of the previous function.
    CImg<T>& load_rgba(std::FILE *const file, const char *const filename, const unsigned int dimw, const unsigned int dimh) {
      const int cimg_iobuffer = 12*1024*1024;
      std::FILE *const nfile = file?file:cimg::fopen(filename,"rb");
      CImg<unsigned char> raw;
      assign(dimw,dimh,1,4);
      T
        *ptr_r = ptr(0,0,0,0),
        *ptr_g = ptr(0,0,0,1),
        *ptr_b = ptr(0,0,0,2),
        *ptr_a = ptr(0,0,0,3);
      for (int toread = (int)size(); toread>0; ) {
        raw.assign(cimg::min(toread,cimg_iobuffer));
        cimg::fread(raw.data,raw.width,nfile);
        toread-=raw.width;
        const unsigned char *ptrs = raw.data;
        for (unsigned int off = raw.width/4; off; --off) {
          *(ptr_r++) = (T)*(ptrs++);
          *(ptr_g++) = (T)*(ptrs++);
          *(ptr_b++) = (T)*(ptrs++);
          *(ptr_a++) = (T)*(ptrs++);
        }
      }
      if (!file) cimg::fclose(nfile);
      return *this;
    }

    //! Load an image from a RGBA file.
    static CImg<T> get_load_rgba(const char *const filename, const unsigned int dimw, const unsigned int dimh) {
      return CImg<T>().load_rgba(0,filename,dimw,dimh);
    }

    //! In-place version of the previous function.
    CImg<T>& load_rgba(const char *const filename, const unsigned int dimw, const unsigned int dimh) {
      return load_rgba(0,filename,dimw,dimh);
    }

    //! Load an image from a RGB file.
    static CImg<T> get_load_rgb(std::FILE *const file, const char *const filename, const unsigned int dimw, const unsigned int dimh) {
      return CImg<T>().load_rgb(file,filename,dimw,dimh);
    }

    //! In-place version of the previous function.
    CImg<T>& load_rgb(std::FILE *const file, const char *const filename, const unsigned int dimw, const unsigned int dimh) {
      const int cimg_iobuffer = 12*1024*1024;
      std::FILE *const nfile = file?file:cimg::fopen(filename,"rb");
      CImg<unsigned char> raw;
      assign(dimw,dimh,1,3);
      T
        *ptr_r = ptr(0,0,0,0),
        *ptr_g = ptr(0,0,0,1),
        *ptr_b = ptr(0,0,0,2);
      for (int toread = (int)size(); toread>0; ) {
        raw.assign(cimg::min(toread,cimg_iobuffer));
        cimg::fread(raw.data,raw.width,nfile);
        toread-=raw.width;
        const unsigned char *ptrs = raw.data;
        for (unsigned int off = raw.width/3; off; --off) {
          *(ptr_r++) = (T)*(ptrs++);
          *(ptr_g++) = (T)*(ptrs++);
          *(ptr_b++) = (T)*(ptrs++);
        }
      }
      if (!file) cimg::fclose(nfile);
      return *this;
    }

    //! Load an image from a RGB file.
    static CImg<T> get_load_rgb(const char *const filename, const unsigned int dimw, const unsigned int dimh) {
      return CImg<T>().load_rgb(0,filename,dimw,dimh);
    }

    CImg<T>& load_rgb(const char *const filename, const unsigned int dimw, const unsigned int dimh) {
      return load_rgb(0,filename,dimw,dimh);
    }

    //! Load an image from an INRIMAGE-4 file.
    static CImg<T> get_load_inr(std::FILE *const file, const char *const filename=0, float *voxsize=0) {
      return CImg<T>().load_inr(file,filename,voxsize);
    }

    //! In-place version of the previous function.
    CImg<T>& load_inr(std::FILE *const file, const char *const filename=0, float *const voxsize=0) {

#define cimg_load_inr_case(Tf,sign,pixsize,Ts) \
     if (!loaded && fopt[6]==pixsize && fopt[4]==Tf && fopt[5]==sign) { \
        Ts *xval, *val = new Ts[fopt[0]*fopt[3]]; \
        cimg_forYZ(*this,y,z) { \
            cimg::fread(val,fopt[0]*fopt[3],nfile); \
            if (fopt[7]!=endian) cimg::endian_swap(val,fopt[0]*fopt[3]); \
            xval = val; cimg_forX(*this,x) cimg_forV(*this,k) (*this)(x,y,z,k) = (T)*(xval++); \
          } \
        delete[] val; \
        loaded = true; \
      }

      std::FILE *const nfile = file?file:cimg::fopen(filename,"rb");
      int fopt[8], endian=cimg::endian()?1:0;
      bool loaded = false;
      if (voxsize) voxsize[0]=voxsize[1]=voxsize[2]=1;
      _load_inr(nfile,fopt,voxsize);
      assign(fopt[0],fopt[1],fopt[2],fopt[3]);
      cimg_load_inr_case(0,0,8, unsigned char);
      cimg_load_inr_case(0,1,8, char);
      cimg_load_inr_case(0,0,16,unsigned short);
      cimg_load_inr_case(0,1,16,short);
      cimg_load_inr_case(0,0,32,unsigned int);
      cimg_load_inr_case(0,1,32,int);
      cimg_load_inr_case(1,0,32,float);
      cimg_load_inr_case(1,1,32,float);
      cimg_load_inr_case(1,0,64,double);
      cimg_load_inr_case(1,1,64,double);
      if (!loaded) {
        if (!file) cimg::fclose(nfile);
        throw CImgIOException("CImg<%s>::load_inr() : File '%s', cannot read images of the type specified in the file",
                              pixel_type(),filename?filename:"(FILE*)");
      }
      if (!file) cimg::fclose(nfile);
      return *this;
    }

    static void _load_inr(std::FILE *file, int out[8], float *const voxsize=0) {
      char item[1024], tmp1[64], tmp2[64];
      out[0] = out[1] = out[2] = out[3] = out[5] = 1; out[4] = out[6] = out[7] = -1;
      if (std::fscanf(file,"%63s",item) == EOF || cimg::strncasecmp(item,"#INRIMAGE-4#{",13)!=0)
        throw CImgIOException("CImg<%s>::load_inr() : File does not appear to be a valid INR file.\n"
                              "(INRIMAGE-4 identifier not found)",pixel_type());
      while (std::fscanf(file," %63[^\n]%*c",item)!=EOF && cimg::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::strcpy(tmp1,tmp2);
        case 1:
          if (!cimg::strncasecmp(tmp1,"int",3)   || !cimg::strncasecmp(tmp1,"fixed",5))  out[4] = 0;
          if (!cimg::strncasecmp(tmp1,"float",5) || !cimg::strncasecmp(tmp1,"double",6)) out[4] = 1;
          if (!cimg::strncasecmp(tmp1,"packed",6))                                       out[4] = 2;
          if (out[4]>=0) break;
        default: throw CImgIOException("cimg::inr_header_read() : Invalid TYPE '%s'",tmp2);
        }
      }
      if(out[0]<0 || out[1]<0 || out[2]<0 || out[3]<0)
        throw CImgIOException("CImg<%s>::load_inr() : Bad dimensions in .inr file = ( %d , %d , %d , %d )",
                              pixel_type(),out[0],out[1],out[2],out[3]);
      if(out[4]<0 || out[5]<0) throw CImgIOException("CImg<%s>::load_inr() : TYPE is not fully defined",pixel_type());
      if(out[6]<0) throw CImgIOException("CImg<%s>::load_inr() : PIXSIZE is not fully defined",pixel_type());
      if(out[7]<0) throw CImgIOException("CImg<%s>::load_inr() : Big/Little Endian coding type is not defined",pixel_type());
    }

    //! Load an image from an INRIMAGE-4 file.
    static CImg<T> get_load_inr(const char *const filename, float *const voxsize=0) {
      return CImg<T>().load_inr(0,filename,voxsize);
    }

    CImg<T>& load_inr(const char *const filename, float *const voxsize=0) {
      return load_inr(0,filename,voxsize);
    }

    //! Load an image from a PANDORE file.
    static CImg<T> get_load_pandore(std::FILE *const file, const char *const filename=0) {
      return CImg<T>().load_pandore(file,filename);
    }

    //! In-place version of the previous function.
    CImg<T>& load_pandore(std::FILE *const file, const char *const filename) {

#define cimg_load_pandore_case(nid,nbdim,nwidth,nheight,ndepth,ndim,stype) \
      case nid: { \
        cimg::fread(dims,nbdim,nfile); \
        if (endian) cimg::endian_swap(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::endian_swap(buffer,siz); \
        T *ptrd = data; \
        cimg_foroff(*this,off) *(ptrd++) = (T)*(buffer++); \
        buffer-=siz; \
        delete[] buffer; \
       } \
       break;

      std::FILE *const nfile = file?file:cimg::fopen(filename,"rb");
      typedef unsigned char uchar;
      typedef unsigned short ushort;
      typedef unsigned int uint;
      typedef unsigned long ulong;
      char tmp[32];
      cimg::fread(tmp,12,nfile);
      if (cimg::strncasecmp("PANDORE",tmp,7)) {
        if (!file) cimg::fclose(nfile);
        throw CImgIOException("CImg<%s>::load_pandore() : File '%s' is not a valid PANDORE file.\n"
                              "(PANDORE identifier not found).",pixel_type(),filename?filename:"(FILE*)");
      }
      unsigned int imageid,dims[8];
      int ptbuf[4];
      cimg::fread(&imageid,1,nfile);
      const bool endian = (imageid>255);
      if (endian) cimg::endian_swap(imageid);

      cimg::fread(tmp,20,nfile);
      switch (imageid) {
        cimg_load_pandore_case(2,2,dims[1],1,1,1,uchar);
        cimg_load_pandore_case(3,2,dims[1],1,1,1,long);
        cimg_load_pandore_case(4,2,dims[1],1,1,1,float);
        cimg_load_pandore_case(5,3,dims[2],dims[1],1,1,uchar);
        cimg_load_pandore_case(6,3,dims[2],dims[1],1,1,long);
        cimg_load_pandore_case(7,3,dims[2],dims[1],1,1,float);
        cimg_load_pandore_case(8,4,dims[3],dims[2],dims[1],1,uchar);
        cimg_load_pandore_case(9,4,dims[3],dims[2],dims[1],1,long);
        cimg_load_pandore_case(10,4,dims[3],dims[2],dims[1],1,float);

      case 11: { // Region 1D
        cimg::fread(dims,3,nfile);
        if (endian) cimg::endian_swap(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::endian_swap(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::endian_swap(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::endian_swap(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::endian_swap(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::endian_swap(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::endian_swap(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::endian_swap(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::endian_swap(buffer,siz);
            T *ptrd = data;
            cimg_foroff(*this,off) *(ptrd++) = (T)*(buffer++);
            buffer-=siz;
            delete[] buffer;
          }
        }
      }
        break;
        cimg_load_pandore_case(16,4,dims[2],dims[1],1,3,uchar);
        cimg_load_pandore_case(17,4,dims[2],dims[1],1,3,long);
        cimg_load_pandore_case(18,4,dims[2],dims[1],1,3,float);
        cimg_load_pandore_case(19,5,dims[3],dims[2],dims[1],3,uchar);
        cimg_load_pandore_case(20,5,dims[3],dims[2],dims[1],3,long);
        cimg_load_pandore_case(21,5,dims[3],dims[2],dims[1],3,float);
        cimg_load_pandore_case(22,2,dims[1],1,1,dims[0],uchar);
        cimg_load_pandore_case(23,2,dims[1],1,1,dims[0],long);
        cimg_load_pandore_case(24,2,dims[1],1,1,dims[0],ulong);
        cimg_load_pandore_case(25,2,dims[1],1,1,dims[0],float);
        cimg_load_pandore_case(26,3,dims[2],dims[1],1,dims[0],uchar);
        cimg_load_pandore_case(27,3,dims[2],dims[1],1,dims[0],long);
        cimg_load_pandore_case(28,3,dims[2],dims[1],1,dims[0],ulong);
        cimg_load_pandore_case(29,3,dims[2],dims[1],1,dims[0],float);
        cimg_load_pandore_case(30,4,dims[3],dims[2],dims[1],dims[0],uchar);
        cimg_load_pandore_case(31,4,dims[3],dims[2],dims[1],dims[0],long);
        cimg_load_pandore_case(32,4,dims[3],dims[2],dims[1],dims[0],ulong);
        cimg_load_pandore_case(33,4,dims[3],dims[2],dims[1],dims[0],float);
      case 34: // Points 1D
        cimg::fread(ptbuf,1,nfile);
        if (endian) cimg::endian_swap(ptbuf,1);
        assign(1); (*this)(0) = (T)ptbuf[0];
        break;
      case 35: // Points 2D
        cimg::fread(ptbuf,2,nfile);
        if (endian) cimg::endian_swap(ptbuf,2);
        assign(2); (*this)(0) = (T)ptbuf[1]; (*this)(1) = (T)ptbuf[0];
        break;
      case 36: // Points 3D
        cimg::fread(ptbuf,3,nfile);
        if (endian) cimg::endian_swap(ptbuf,3);
        assign(3); (*this)(0) = (T)ptbuf[2]; (*this)(1) = (T)ptbuf[1]; (*this)(2) = (T)ptbuf[0];
        break;
      default:
        if (!file) cimg::fclose(nfile);
        throw CImgIOException("CImg<%s>::load_pandore() : File '%s', cannot read images with ID_type=%u",
                              pixel_type(),filename?filename:"(FILE*)",imageid);
      }
      if (!file) cimg::fclose(nfile);
      return *this;
    }

    //! Load an image from a PANDORE file.
    static CImg<T> get_load_pandore(const char *const filename) {
      return CImg<T>().load_pandore(0,filename);
    }

    CImg<T>& load_pandore(const char *const filename) {
      return load_pandore(0,filename);
    }

    //! Load an image from an ANALYZE7.5/NIFTI file.
    static CImg<T> get_load_analyze(const char *const filename, float *const voxsize=0) {
      return CImg<T>().load_analyze(filename,voxsize);
    }

    CImg<T>& load_analyze(const char *const filename, float *const voxsize=0) {
      std::FILE *file_header = 0, *file = 0;
      bool error_file = false;
      char body[1024];
      const char *ext = cimg::filename_split(filename,body);
      if (!cimg::strncasecmp(ext,"nii",3)) file_header = cimg::fopen(filename,"rb");
      else {
        if (!cimg::strncasecmp(ext,"hdr",3) ||
            !cimg::strncasecmp(ext,"img",3)) {
          std::sprintf(body+cimg::strlen(body),".hdr");
          file_header = cimg::fopen(body,"rb");
          if (!file_header) error_file = true;
          else {
            std::sprintf(body+cimg::strlen(body)-3,"img");
            file = cimg::fopen(body,"rb");
            if (!file) { cimg::fclose(file_header); error_file = true; }
          }
        }
      }
      if (error_file) throw CImgIOException("CImg<%s>::load_analyze() : Filename '%s', not recognized as an Analyze 7.5 or NIFTI file.",
                                            pixel_type(),filename);

      // Read header
      bool endian = false;
      unsigned int header_size;
      cimg::fread(&header_size,1,file_header);
      if (header_size>=4096) { endian = true; cimg::endian_swap(header_size); }
      unsigned char *header = new unsigned char[header_size];
      cimg::fread(header+4,header_size-4,file_header);
      if (file) cimg::fclose(file_header);
      if (endian) {
        cimg::endian_swap((short*)(header+40),5);
        cimg::endian_swap((short*)(header+70),1);
        cimg::endian_swap((short*)(header+72),1);
        cimg::endian_swap((float*)(header+76),4);
        cimg::endian_swap((float*)(header+112),1);
      }
      unsigned short *dim = (unsigned short*)(header+40), dimx=1, dimy=1, dimz=1, dimv=1;
      if (!dim[0]) cimg::warn("CImg<%s>::load_analyze() : Specified image has zero dimensions.",pixel_type());
      if (dim[0]>4) cimg::warn("CImg<%s>::load_analyze() : Number of image dimension is %d, reading only the 4 first dimensions",
                               pixel_type(),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
      std::FILE *nfile = file?file:file_header;
      assign(dimx,dimy,dimz,dimv);
      switch (datatype) {
      case 2: {
        unsigned char *buffer = new unsigned char[dimx*dimy*dimz*dimv];
        cimg::fread(buffer,dimx*dimy*dimz*dimv,nfile);
        cimg_foroff(*this,off) data[off] = (T)(buffer[off]*scalefactor);
        delete[] buffer;
      } break;
      case 4: {
        short *buffer = new short[dimx*dimy*dimz*dimv];
        cimg::fread(buffer,dimx*dimy*dimz*dimv,nfile);
        if (endian) cimg::endian_swap(buffer,dimx*dimy*dimz*dimv);
        cimg_foroff(*this,off) data[off] = (T)(buffer[off]*scalefactor);
        delete[] buffer;
      } break;
      case 8: {
        int *buffer = new int[dimx*dimy*dimz*dimv];
        cimg::fread(buffer,dimx*dimy*dimz*dimv,nfile);
        if (endian) cimg::endian_swap(buffer,dimx*dimy*dimz*dimv);
        cimg_foroff(*this,off) data[off] = (T)(buffer[off]*scalefactor);
        delete[] buffer;
      } break;
      case 16: {
        float *buffer = new float[dimx*dimy*dimz*dimv];
        cimg::fread(buffer,dimx*dimy*dimz*dimv,nfile);
        if (endian) cimg::endian_swap(buffer,dimx*dimy*dimz*dimv);
        cimg_foroff(*this,off) data[off] = (T)(buffer[off]*scalefactor);
        delete[] buffer;
      } break;
      case 64: {
        double *buffer = new double[dimx*dimy*dimz*dimv];
        cimg::fread(buffer,dimx*dimy*dimz*dimv,nfile);
        if (endian) cimg::endian_swap(buffer,dimx*dimy*dimz*dimv);
        cimg_foroff(*this,off) data[off] = (T)(buffer[off]*scalefactor);
        delete[] buffer;
      } break;
      default:
        cimg::fclose(nfile);
        throw CImgIOException("CImg<%s>::load_analyze() : File '%s, cannot read images with 'datatype = %d'",
                              pixel_type(),filename,datatype);
      }
      cimg::fclose(nfile);
      return *this;
    }

    //! Load a 3D object from a .OFF file (GeomView 3D object files).
    template<typename tf, typename tc>
    static CImg<T> get_load_off(const char *const filename, CImgList<tf>& primitives, CImgList<tc>& colors,
                                const bool invert_faces=false) {
      return CImg<T>().load_off(filename,primitives,colors,invert_faces);
    }

    //! In-place version of the previous function.
    template<typename tf, typename tc>
    CImg<T>& load_off(const char *const filename, CImgList<tf>& primitives, CImgList<tc>& colors, const bool invert_faces=false) {
      std::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(file,"%255[^\n] ",line); } while (!err || (err==1 && line[0]=='#'));
      if (cimg::strncasecmp(line,"OFF",3) && cimg::strncasecmp(line,"COFF",4)) {
        cimg::fclose(file);
        throw CImgIOException("CImg<%s>::load_off() : File '%s', keyword 'OFF' not found.",pixel_type(),filename);
      }
      do { err = std::fscanf(file,"%255[^\n] ",line); } while (!err || (err==1 && line[0]=='#'));

      if ((err = std::sscanf(line,"%u%u%*[^\n] ",&nb_points,&nb_primitives))!=2) {
        cimg::fclose(file);
        throw CImgIOException("CImg<%s>::load_off() : File '%s', invalid vertices/primitives numbers.",pixel_type(),filename);
      }

      // Read points data
      assign(nb_points,3);
      float X = 0, Y = 0, Z = 0;
      cimg_forX(*this,l) {
        do { err = std::fscanf(file,"%255[^\n] ",line); } while (!err || (err==1 && line[0]=='#'));
        if ((err = std::sscanf(line,"%f%f%f%*[^\n] ",&X,&Y,&Z))!=3) {
          cimg::fclose(file);
          throw CImgIOException("CImg<%s>::load_off() : File '%s', cannot read point %u/%u.\n",pixel_type(),filename,l+1,nb_points);
        }
        (*this)(l,0) = (T)X; (*this)(l,1) = (T)Y; (*this)(l,2) = (T)Z;
      }

      // Read primitive data
      primitives.assign();
      colors.assign();
      bool stopflag = false;
      while (!stopflag) {
        float c0 = 0.7f, c1 = 0.7f, c2 = 0.7f;
        unsigned int prim = 0, i0 = 0, i1 = 0, i2 = 0, i3 = 0, i4 = 0, i5 = 0, i6 = 0, i7 = 0;
        line[0]='\0';
        if ((err = std::fscanf(file,"%u",&prim))!=1) stopflag=true;
        else {
          ++nb_read;
          switch (prim) {
          case 1: {
            if ((err = std::fscanf(file,"%u%255[^\n] ",&i0,line))<2) {
              cimg::warn("CImg<%s>::load_off() : File '%s', invalid primitive %u/%u.",
                         pixel_type(),filename,nb_read,nb_primitives);
              std::fscanf(file,"%*[^\n] ");
            } else {
              std::sscanf(line,"%f%f%f",&c0,&c1,&c2);
              primitives.insert(CImg<tf>::vector(i0));
              colors.insert(CImg<tc>::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255)));
            }
          } break;
          case 2: {
            if ((err = std::fscanf(file,"%u%u%255[^\n] ",&i0,&i1,line))<2) {
              cimg::warn("CImg<%s>::load_off() : File '%s', invalid primitive %u/%u.",
                         pixel_type(),filename,nb_read,nb_primitives);
              std::fscanf(file,"%*[^\n] ");
            } else {
              std::sscanf(line,"%f%f%f",&c0,&c1,&c2);
              primitives.insert(CImg<tf>::vector(i0,i1));
              colors.insert(CImg<tc>::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255)));
            }
          } break;
          case 3: {
            if ((err = std::fscanf(file,"%u%u%u%255[^\n] ",&i0,&i1,&i2,line))<3) {
              cimg::warn("CImg<%s>::load_off() : File '%s', invalid primitive %u/%u.",
                         pixel_type(),filename,nb_read,nb_primitives);
              std::fscanf(file,"%*[^\n] ");
            } else {
              std::sscanf(line,"%f%f%f",&c0,&c1,&c2);
              if (invert_faces) primitives.insert(CImg<tf>::vector(i0,i1,i2));
              else primitives.insert(CImg<tf>::vector(i0,i2,i1));
              colors.insert(CImg<tc>::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255)));
            }
          } break;
          case 4: {
            if ((err = std::fscanf(file,"%u%u%u%u%255[^\n] ",&i0,&i1,&i2,&i3,line))<4) {
              cimg::warn("CImg<%s>::load_off() : File '%s', invalid primitive %u/%u.",
                         pixel_type(),filename,nb_read,nb_primitives);
              std::fscanf(file,"%*[^\n] ");
            } else {
              std::sscanf(line,"%f%f%f",&c0,&c1,&c2);
              if (invert_faces) primitives.insert(CImg<tf>::vector(i0,i1,i2,i3));
              else primitives.insert(CImg<tf>::vector(i0,i3,i2,i1));
              colors.insert(CImg<tc>::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255),(tc)(c2*255)));
            }
          } break;
          case 5: {
            if ((err = std::fscanf(file,"%u%u%u%u%u%255[^\n] ",&i0,&i1,&i2,&i3,&i4,line))<5) {
              cimg::warn("CImg<%s>::load_off() : File '%s', invalid primitive %u/%u.",
                         pixel_type(),filename,nb_read,nb_primitives);
              std::fscanf(file,"%*[^\n] ");
            } else {
              std::sscanf(line,"%f%f%f",&c0,&c1,&c2);
              if (invert_faces) {
                primitives.insert(CImg<tf>::vector(i0,i1,i2,i3));
                primitives.insert(CImg<tf>::vector(i0,i3,i4));
              }
              else {
                primitives.insert(CImg<tf>::vector(i0,i3,i2,i1));
                primitives.insert(CImg<tf>::vector(i0,i4,i3));
              }
              colors.insert(2,CImg<tc>::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255),(tc)(c2*255)));
              ++nb_primitives;
            }
          } break;
          case 6: {
            if ((err = std::fscanf(file,"%u%u%u%u%u%u%255[^\n] ",&i0,&i1,&i2,&i3,&i4,&i5,line))<6) {
              cimg::warn("CImg<%s>::load_off() : File '%s', invalid primitive %u/%u.",
                         pixel_type(),filename,nb_read,nb_primitives);
              std::fscanf(file,"%*[^\n] ");
            } else {
              std::sscanf(line,"%f%f%f",&c0,&c1,&c2);
              if (invert_faces) {
                primitives.insert(CImg<tf>::vector(i0,i1,i2,i3));
                primitives.insert(CImg<tf>::vector(i0,i3,i4,i5));
              }
              else {
                primitives.insert(CImg<tf>::vector(i0,i3,i2,i1));
                primitives.insert(CImg<tf>::vector(i0,i5,i4,i3));
              }
              colors.insert(2,CImg<tc>::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255),(tc)(c2*255)));
              ++nb_primitives;
            }
          } break;
          case 7: {
            if ((err = std::fscanf(file,"%u%u%u%u%u%u%u%255[^\n] ",&i0,&i1,&i2,&i3,&i4,&i5,&i6,line))<7) {
              cimg::warn("CImg<%s>::load_off() : File '%s', invalid primitive %u/%u.",
                         pixel_type(),filename,nb_read,nb_primitives);
              std::fscanf(file,"%*[^\n] ");
            } else {
              std::sscanf(line,"%f%f%f",&c0,&c1,&c2);
              if (invert_faces) {
                primitives.insert(CImg<tf>::vector(i0,i1,i3,i4));
                primitives.insert(CImg<tf>::vector(i0,i4,i5,i6));
                primitives.insert(CImg<tf>::vector(i1,i2,i3));
              }
              else {
                primitives.insert(CImg<tf>::vector(i0,i4,i3,i1));
                primitives.insert(CImg<tf>::vector(i0,i6,i5,i4));
                primitives.insert(CImg<tf>::vector(i3,i2,i1));
              }
              colors.insert(2,CImg<tc>::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255),(tc)(c2*255)));
              ++(++nb_primitives);
            }
          } break;
          case 8: {
            if ((err = std::fscanf(file,"%u%u%u%u%u%u%u%u%255[^\n] ",&i0,&i1,&i2,&i3,&i4,&i5,&i6,&i7,line))<7) {
              cimg::warn("CImg<%s>::load_off() : File '%s', invalid primitive %u/%u.",
                         pixel_type(),filename,nb_read,nb_primitives);
              std::fscanf(file,"%*[^\n] ");
            } else {
              std::sscanf(line,"%f%f%f",&c0,&c1,&c2);
              if (invert_faces) {
                primitives.insert(CImg<tf>::vector(i0,i1,i2,i3));
                primitives.insert(CImg<tf>::vector(i0,i3,i4,i5));
                primitives.insert(CImg<tf>::vector(i0,i5,i6,i7));
              }
              else {
                primitives.insert(CImg<tf>::vector(i0,i3,i2,i1));
                primitives.insert(CImg<tf>::vector(i0,i5,i4,i3));
                primitives.insert(CImg<tf>::vector(i0,i7,i6,i5));
              }
              colors.insert(2,CImg<tc>::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255),(tc)(c2*255)));
              ++(++nb_primitives);
            }
          } break;
          default:
            cimg::warn("CImg<%s>::load_off() : File '%s', invalid primitive %u/%u (%u vertices).",
                       pixel_type(),filename,nb_read,nb_primitives,prim);
            std::fscanf(file,"%*[^\n] ");
            break;
          }
        }
      }
      cimg::fclose(file);
      if (primitives.size!=nb_primitives)
        cimg::warn("CImg<%s>::load_off() : File '%s', read only %u primitives instead of %u as claimed in the header.",
                   pixel_type(),filename,primitives.size,nb_primitives);
      return *this;
    }

    //! Load an image from a PAR-REC (Philips) file.
    static CImg<T> get_load_parrec(const char *const filename, const char axis='v', const char align='p') {
      return CImg<T>().load_parrec(filename,axis,align);
    }

    //! In-place version of the previous function.
    CImg<T>& load_parrec(const char *const filename, const char axis='v', const char align='p') {
      CImgList<T> list;
      list.load_parrec(filename);
      if (list.size==1) return list[0].transfer_to(*this);
      return assign(list.get_append(axis,align));
    }

    //! Load an image (list) from a .cimg file.
    static CImg<T> get_load_cimg(std::FILE *const file, const char axis='z', const char align='p') {
      return CImg<T>().load_cimg(file,axis,align);
    }

    //! In-place version of the previous function.
    CImg<T>& load_cimg(std::FILE *const file, const char axis='z', const char align='p') {
      CImgList<T> list;
      list.load_cimg(file);
      if (list.size==1) return list[0].transfer_to(*this);
      return assign(list.get_append(axis,align));
    }

    //! Load an image (list) from a .cimg file.
    static CImg<T> get_load_cimg(const char *const filename, const char axis='z', const char align='p') {
      return CImg<T>().load_cimg(filename,axis,align);
    }

    //! In-place version of the previous function.
    CImg<T>& load_cimg(const char *const filename, const char axis='z', const char align='p') {
      CImgList<T> list;
      list.load_cimg(filename);
      if (list.size==1) return list[0].transfer_to(*this);
      return assign(list.get_append(axis,align));
    }

    //! Load a sub-image (list) from a .cimg file.
    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 v0,
                                 const unsigned int x1, const unsigned int y1, const unsigned int z1, const unsigned int v1,
                                 const char axis='z', const char align='p') {
      return CImg<T>().load_cimg(file,n0,n1,x0,y0,z0,v0,x1,y1,z1,v1,axis,align);
    }

    //! In-place version of the previous function.
    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 v0,
                       const unsigned int x1, const unsigned int y1, const unsigned int z1, const unsigned int v1,
                       const char axis='z', const char align='p') {
      CImgList<T> list;
      list.load_cimg(file,n0,n1,x0,y0,z0,v0,x1,y1,z1,v1);
      if (list.size==1) return list[0].transfer_to(*this);
      return assign(list.get_append(axis,align));
    }

    //! Load a sub-image (list) from a .cimg file.
    static CImg<T> get_load_cimg(const char *const filename,
                                 const unsigned int n0, const unsigned int n1,
                                 const unsigned int x0, const unsigned int y0, const unsigned int z0, const unsigned int v0,
                                 const unsigned int x1, const unsigned int y1, const unsigned int z1, const unsigned int v1,
                                 const char axis='z', const char align='p') {
      return CImg<T>().load_cimg(filename,n0,n1,x0,y0,z0,v0,x1,y1,z1,v1,axis,align);
    }

    //! In-place version of the previous function.
    CImg<T>& load_cimg(const char *const filename,
                       const unsigned int n0, const unsigned int n1,
                       const unsigned int x0, const unsigned int y0, const unsigned int z0, const unsigned int v0,
                       const unsigned int x1, const unsigned int y1, const unsigned int z1, const unsigned int v1,
                       const char axis='z', const char align='p') {
      CImgList<T> list;
      list.load_cimg(filename,n0,n1,x0,y0,z0,v0,x1,y1,z1,v1);
      if (list.size==1) return list[0].transfer_to(*this);
      return assign(list.get_append(axis,align));
    }

    //! Load an image sequence from a YUV file.
    static CImg<T> get_load_yuv(std::FILE *const file, const char *const filename,
                                const unsigned int sizex, const unsigned int sizey=1,
                                const unsigned int first_frame=0, const unsigned int last_frame=~0U,
				const unsigned int step_frame=1, const bool yuv2rgb=true, const char axis='z', const char align='p') {
      return CImgList<T>().load_yuv(file,filename,sizex,sizey,first_frame,last_frame,step_frame,yuv2rgb).get_append(axis,align);
    }

    //! In-place version of the previous function.
    CImg<T>& load_yuv(std::FILE *const file, const char *const filename,
                      const unsigned int sizex, const unsigned int sizey=1,
                      const unsigned int first_frame=0, const unsigned int last_frame=~0U,
		      const unsigned int step_frame=1, const bool yuv2rgb=true, const char axis='z', const char align='p') {
      return get_load_yuv(file,filename,sizex,sizey,first_frame,last_frame,step_frame,yuv2rgb,axis,align).transfer_to(*this);
    }

    //! Load an image sequence from a YUV file.
    static CImg<T> get_load_yuv(const char *const filename,
                                const unsigned int sizex, const unsigned int sizey=1,
                                const unsigned int first_frame=0, const unsigned int last_frame=~0U,
                                const unsigned int step_frame=1, const bool yuv2rgb=true, const char axis='z', const char align='p') {
      return CImgList<T>().load_yuv(filename,sizex,sizey,first_frame,last_frame,step_frame,yuv2rgb).get_append(axis,align);
    }

    //! In-place version of the previous function.
    CImg<T>& load_yuv(const char *const filename,
                      const unsigned int sizex, const unsigned int sizey=1,
                      const unsigned int first_frame=0, const unsigned int last_frame=~0U,
                      const unsigned int step_frame=1, const bool yuv2rgb=true, const char axis='z', const char align='p') {
      return get_load_yuv(0,filename,sizex,sizey,first_frame,last_frame,step_frame,yuv2rgb,axis,align).transfer_to(*this);
    }

    //! Load a DICOM image file, using XMedcon's external tool 'medcon'.
    static CImg<T> get_load_medcon_external(const char *const filename) {
      return CImg<T>().load_medcon_external(filename);
    }

    //! In-place version of the previous function.
    CImg<T>& load_medcon_external(const char *const filename) {
      char command[1024], filetmp[512], body[512];
      cimg::fclose(cimg::fopen(filename,"r"));
      std::FILE *file;
      do {
        std::sprintf(filetmp,"%s.hdr",cimg::filenamerand());
        if ((file=std::fopen(filetmp,"rb"))!=0) std::fclose(file);
      } while (file);
      std::sprintf(command,"%s -w -c anlz -o %s -f %s",cimg::medcon_path(),filetmp,filename);
      cimg::system(command);
      cimg::filename_split(filetmp,body);
      std::sprintf(command,"m000-%s.hdr",body);
      file = std::fopen(command,"rb");
      if (!file) {
        throw CImgIOException("CImg<%s>::load_medcon_external() : Failed to open image '%s'.\n\n"
                              "Path of 'medcon' : \"%s\"\n"
                              "Path of temporary filename : \"%s\"",
                              pixel_type(),filename,cimg::medcon_path(),filetmp);
      } else cimg::fclose(file);
      load_analyze(command);
      std::remove(command);
      std::sprintf(command,"m000-%s.img",body);
      std::remove(command);
      return *this;
    }

    //! Load an image using ImageMagick's external tool 'convert'.
    static CImg<T> get_load_imagemagick_external(const char *const filename) {
      return CImg<T>().load_imagemagick_external(filename);
    }

    //! In-place version of the previous function.
    CImg<T>& load_imagemagick_external(const char *const filename) {
      char command[1024], filetmp[512];
      std::FILE *file = 0;
      do {
        std::sprintf(filetmp,"%s%s%s.ppm",cimg::temporary_path(),cimg_OS==2?"\\":"/",cimg::filenamerand());
        if ((file=std::fopen(filetmp,"rb"))!=0) std::fclose(file);
      } while (file);
      std::sprintf(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<%s>::load_imagemagick_external() : Failed to open image '%s'.\n\n"
                              "Path of 'ImageMagick's convert' : \"%s\"\n"
                              "Path of temporary filename : \"%s\"",
                              pixel_type(),filename,cimg::imagemagick_path(),filetmp);
      } else cimg::fclose(file);
      load_pnm(filetmp);
      std::remove(filetmp);
      return *this;
    }

    //! Load an image using GraphicsMagick's external tool 'gm'.
    static CImg<T> get_load_graphicsmagick_external(const char *const filename) {
      return CImg<T>().load_graphicsmagick_external(filename);
    }

    //! In-place version of the previous function.
    CImg<T>& load_graphicsmagick_external(const char *const filename) {
      char command[1024], filetmp[512];
      std::FILE *file = 0;
      do {
        std::sprintf(filetmp,"%s%s%s.ppm",cimg::temporary_path(),cimg_OS==2?"\\":"/",cimg::filenamerand());
        if ((file=std::fopen(filetmp,"rb"))!=0) std::fclose(file);
      } while (file);
      std::sprintf(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<%s>::load_graphicsmagick_external() : Failed to open image '%s'.\n\n"
                              "Path of 'GraphicsMagick's gm' : \"%s\"\n"
                              "Path of temporary filename : \"%s\"",
                              pixel_type(),filename,cimg::graphicsmagick_path(),filetmp);
      } else cimg::fclose(file);
      load_pnm(filetmp);
      std::remove(filetmp);
      return *this;
    }

    //! Load a video sequence using FFMPEG av's libraries.
    static CImg<T> get_load_ffmpeg(const char *const filename, const unsigned int first_frame=0, const unsigned int last_frame=~0U,
				   const unsigned int step_frame=1, const bool pixel_format=true, const bool resume=false,
				   const char axis='z', const char align='p') {
      return CImgList<T>().load_ffmpeg(filename,first_frame,last_frame,step_frame,pixel_format,resume).get_append(axis,align);
    }

    // In-place version of the previous function
    CImg<T>& load_ffmpeg(const char *const filename, const unsigned int first_frame=0, const unsigned int last_frame=~0U,
			 const unsigned int step_frame=1, const bool pixel_format=true, const bool resume=false,
			 const char axis='z', const char align='p') {
      return get_load_ffmpeg(filename,first_frame,last_frame,step_frame,pixel_format,resume,axis,align).transfer_to(*this);
    }

    //! Load a video sequence using FFMPEG's external tool 'ffmpeg'
    static CImg<T> get_load_ffmpeg_external(const char *const filename, const char axis='z', const char align='p') {
      return CImgList<T>().load_ffmpeg_external(filename).get_append(axis,align);
    }

    //! In-place version of the previous function.
    CImg<T>& load_ffmpeg_external(const char *const filename, const char axis='z', const char align='p') {
      return get_load_ffmpeg_external(filename,axis,align).transfer_to(*this);
    }

    //! Load an image using ImageMagick's or GraphicsMagick's executables.
    static CImg<T> get_load_other(const char *const filename) {
      return CImg<T>().load_other(filename);
    }

    //! In-place version of the previous function.
    CImg<T>& load_other(const char *const filename) {
      const unsigned int odebug = cimg::exception_mode();
      cimg::exception_mode() = 0;
      try { load_magick(filename); }
      catch (CImgException&) {
        try { load_imagemagick_external(filename); }
        catch (CImgException&) {
          try { load_graphicsmagick_external(filename); }
          catch (CImgException&) {
            assign();
          }
        }
      }
      cimg::exception_mode() = odebug;
      if (is_empty())
        throw CImgIOException("CImg<%s>::load_other() : Failed to open image file '%s'.\n",
                              pixel_type(),filename);
      return *this;
    }

    //! Save the image as a file.
    /**
       The used file format is defined by the file extension in the filename \p filename.\n
       Parameter \p number can be used to add a 6-digit number to the filename before saving.\n
       If \p normalize is true, a normalized version of the image (between [0,255]) is saved.
    **/
    const CImg<T>& save(const char *const filename, const int number=-1) const {
      if (is_empty()) throw CImgInstanceException("CImg<%s>::save() : Instance image (%u,%u,%u,%u,%p) is empty (file '%s').",
                                                  pixel_type(),width,height,depth,dim,data,filename);
      if (!filename) throw CImgArgumentException("CImg<%s>::save() : Instance image (%u,%u,%u,%u,%p), specified filename is (null).",
                                                 pixel_type(),width,height,depth,dim,data);
      const char *ext = cimg::filename_split(filename);
      char nfilename[1024];
      const char *const fn = (number>=0)?cimg::filename_number(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
      if (!cimg::strcasecmp(ext,"asc")) return save_ascii(fn);
      if (!cimg::strcasecmp(ext,"dlm") ||
          !cimg::strcasecmp(ext,"txt")) return save_dlm(fn);
      if (!cimg::strcasecmp(ext,"cpp") ||
          !cimg::strcasecmp(ext,"hpp") ||
          !cimg::strcasecmp(ext,"h") ||
          !cimg::strcasecmp(ext,"c")) return save_cpp(fn);
      if (!cimg::strcasecmp(ext,"inr")) return save_inr(fn);
      if (!cimg::strcasecmp(ext,"hdr") ||
          !cimg::strcasecmp(ext,"nii")) return save_analyze(fn);
      if (!cimg::strcasecmp(ext,"dcm")) return save_medcon_external(fn);
      if (!cimg::strcasecmp(ext,"pan")) return save_pandore(fn);
      if (!cimg::strcasecmp(ext,"bmp")) return save_bmp(fn);
      if (!cimg::strcasecmp(ext,"png")) return save_png(fn);
      if (!cimg::strcasecmp(ext,"tif")) return save_tiff(fn);
      if (!cimg::strcasecmp(ext,"jpg") ||
          !cimg::strcasecmp(ext,"jpeg")) return save_jpeg(fn);
      if (!cimg::strcasecmp(ext,"rgba")) return save_rgba(fn);
      if (!cimg::strcasecmp(ext,"rgb")) return save_rgb(fn);
      if (!cimg::strcasecmp(ext,"raw")) return save_raw(fn);
      if (!cimg::strcasecmp(ext,"cimg") || ext[0]=='\0') return save_cimg(fn);
      if (!cimg::strcasecmp(ext,"pgm") ||
          !cimg::strcasecmp(ext,"ppm") ||
          !cimg::strcasecmp(ext,"pnm")) return save_pnm(fn);
      if (!cimg::strcasecmp(ext,"yuv")) return save_yuv(fn,true);
      if (!cimg::strcasecmp(ext,"avi") ||
          !cimg::strcasecmp(ext,"mov") ||
          !cimg::strcasecmp(ext,"mpg") ||
          !cimg::strcasecmp(ext,"mpeg")) return save_ffmpeg_external(fn);
      return save_other(fn);
    }

    //! Save the image as an ASCII file (ASCII Raw + simple header).
    const CImg<T>& save_ascii(std::FILE *const file, const char *const filename=0) const {
      if (is_empty()) throw CImgInstanceException("CImg<%s>::save_ascii() : Instance image (%u,%u,%u,%u,%p) is empty (file '%s').",
                                                  pixel_type(),width,height,depth,dim,data,filename?filename:"(unknown)");
      if (!file && !filename) throw CImgArgumentException("CImg<%s>::save_ascii() : Instance image (%u,%u,%u,%u,%p), specified file is (null).",
                                                          pixel_type(),width,height,depth,dim,data);
      std::FILE *const nfile = file?file:cimg::fopen(filename,"w");
      std::fprintf(nfile,"%u %u %u %u\n",width,height,depth,dim);
      const T* ptrs = data;
      cimg_forYZV(*this,y,z,v) {
        cimg_forX(*this,x) 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 a DLM file.
    const CImg<T>& save_dlm(std::FILE *const file, const char *const filename=0) const {
      if (is_empty()) throw CImgInstanceException("CImg<%s>::save_dlm() : Instance image (%u,%u,%u,%u,%p) is empty (file '%s').",
                                                  pixel_type(),width,height,depth,dim,data,filename?filename:"(unknown)");
      if (!file && !filename) throw CImgArgumentException("CImg<%s>::save_dlm() : Instance image (%u,%u,%u,%u,%p), specified file is (null).",
                                                          pixel_type(),width,height,depth,dim,data);
      if (depth>1)
        cimg::warn("CImg<%s>::save_dlm() : Instance image (%u,%u,%u,%u,%p) is volumetric. Pixel values along Z will be unrolled (file '%s').",
                   pixel_type(),width,height,depth,dim,data,filename?filename:"(unknown)");
      if (dim>1)
        cimg::warn("CImg<%s>::save_dlm() : Instance image (%u,%u,%u,%u,%p) is multispectral. Pixel values along V will be unrolled (file '%s').",
                 pixel_type(),width,height,depth,dim,data,filename?filename:"(unknown)");

      std::FILE *const nfile = file?file:cimg::fopen(filename,"w");
      const T* ptrs = data;
      cimg_forYZV(*this,y,z,v) {
        cimg_forX(*this,x) std::fprintf(nfile,"%g%s",(double)*(ptrs++),(x==dimx()-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 C or CPP source file.
    const CImg<T>& save_cpp(std::FILE *const file, const char *const filename=0) const {
      if (is_empty()) throw CImgInstanceException("CImg<%s>::save_cpp() : Instance image (%u,%u,%u,%u,%p) is empty (file '%s').",
                                                  pixel_type(),width,height,depth,dim,data,filename?filename:"(unknown)");
      if (!file && !filename) throw CImgArgumentException("CImg<%s>::save_cpp() : Instance image (%u,%u,%u,%u,%p), specified file is (null).",
                                                          pixel_type(),width,height,depth,dim,data);
      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[0]=='\0') std::sprintf(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,dim,pixel_type(),pixel_type(),varname);
      for (unsigned long 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 PNM file.
    const CImg<T>& save_pnm(std::FILE *const file, const char *const filename=0) const {
      if (is_empty()) throw CImgInstanceException("CImg<%s>::save_pnm() : Instance image (%u,%u,%u,%u,%p) is empty (file '%s').",
                                                  pixel_type(),width,height,depth,dim,data,filename?filename:"(unknown)");
      if (!file && !filename) throw CImgArgumentException("CImg<%s>::save_pnm() : Instance image (%u,%u,%u,%u,%p), specified file is (null).",
                                                          pixel_type(),width,height,depth,dim,data);
      double stmin, stmax = (double)maxmin(stmin);
      if (depth>1)
        cimg::warn("CImg<%s>::save_pnm() : Instance image (%u,%u,%u,%u,%p) is volumetric. Only the first slice will be saved (file '%s').",
                 pixel_type(),width,height,depth,dim,data,filename?filename:"(unknown)");
      if (dim>3)
        cimg::warn("CImg<%s>::save_pnm() : Instance image (%u,%u,%u,%u,%p) is multispectral. Only the three first channels will be saved (file '%s').",
                 pixel_type(),width,height,depth,dim,data,filename?filename:"(unknown)");
      if (stmin<0 || stmax>65535) cimg::warn("CImg<%s>::save_pnm() : Instance image (%u,%u,%u,%u,%p) has pixel values in [%g,%g]. Probable type overflow (file '%s').",pixel_type(),width,height,depth,dim,data,stmin,stmax,filename?filename:"(unknown)");
      std::FILE *const nfile = file?file:cimg::fopen(filename,"wb");
      const T
        *ptrR = ptr(0,0,0,0),
        *ptrG = (dim>=2)?ptr(0,0,0,1):0,
        *ptrB = (dim>=3)?ptr(0,0,0,2):0;
      const unsigned int buf_size = width*height*(dim==1?1:3);

      std::fprintf(nfile,"P%c\n# CREATOR: CImg : Original size=%ux%ux%ux%u\n%u %u\n%u\n",
                   (dim==1?'5':'6'),width,height,depth,dim,width,height,stmax<256?255:65535);

      switch(dim) {
      case 1: { // Scalar image
        if (stmax<256) { // Binary PGM 8 bits
          unsigned char *ptrd = new unsigned char[buf_size], *xptrd = ptrd;
          cimg_forXY(*this,x,y) *(xptrd++) = (unsigned char)*(ptrR++);
          cimg::fwrite(ptrd,buf_size,nfile);
          delete[] ptrd;
        } else {             // Binary PGM 16 bits
          unsigned short *ptrd = new unsigned short[buf_size], *xptrd = ptrd;
          cimg_forXY(*this,x,y) *(xptrd++) = (unsigned short)*(ptrR++);
          if (!cimg::endian()) cimg::endian_swap(ptrd,buf_size);
          cimg::fwrite(ptrd,buf_size,nfile);
          delete[] ptrd;
        }
      } break;
      case 2: { // RG image
        if (stmax<256) { // Binary PPM 8 bits
          unsigned char *ptrd = new unsigned char[buf_size], *xptrd = ptrd;
          cimg_forXY(*this,x,y) {
            *(xptrd++) = (unsigned char)*(ptrR++);
            *(xptrd++) = (unsigned char)*(ptrG++);
            *(xptrd++) = 0;
          }
          cimg::fwrite(ptrd,buf_size,nfile);
          delete[] ptrd;
        } else {             // Binary PPM 16 bits
          unsigned short *ptrd = new unsigned short[buf_size], *xptrd = ptrd;
          cimg_forXY(*this,x,y) {
            *(xptrd++) = (unsigned short)*(ptrR++);
            *(xptrd++) = (unsigned short)*(ptrG++);
            *(xptrd++) = 0;
          }
          if (!cimg::endian()) cimg::endian_swap(ptrd,buf_size);
          cimg::fwrite(ptrd,buf_size,nfile);
          delete[] ptrd;
        }
      } break;
      default: { // RGB image
        if (stmax<256) { // Binary PPM 8 bits
          unsigned char *ptrd = new unsigned char[buf_size], *xptrd = ptrd;
          cimg_forXY(*this,x,y) {
            *(xptrd++) = (unsigned char)*(ptrR++);
            *(xptrd++) = (unsigned char)*(ptrG++);
            *(xptrd++) = (unsigned char)*(ptrB++);
          }
          cimg::fwrite(ptrd,buf_size,nfile);
          delete[] ptrd;
        } else {             // Binary PPM 16 bits
          unsigned short *ptrd = new unsigned short[buf_size], *xptrd = ptrd;
          cimg_forXY(*this,x,y) {
            *(xptrd++) = (unsigned short)*(ptrR++);
            *(xptrd++) = (unsigned short)*(ptrG++);
            *(xptrd++) = (unsigned short)*(ptrB++);
          }
          if (!cimg::endian()) cimg::endian_swap(ptrd,buf_size);
          cimg::fwrite(ptrd,buf_size,nfile);
          delete[] ptrd;
        }
      } break;
      }
      if (!file) cimg::fclose(nfile);
      return *this;
    }

    //! Save the image as a PNM file.
    const CImg<T>& save_pnm(const char *const filename) const {
      return save_pnm(0,filename);
    }

    //! Save an image as a Dicom file (need '(X)Medcon' : http://xmedcon.sourceforge.net )
    const CImg<T>& save_medcon_external(const char *const filename) const {
      if (is_empty()) throw CImgInstanceException("CImg<%s>::save_medcon_external() : Instance image (%u,%u,%u,%u,%p) is empty (file '%s').",
                                                  pixel_type(),width,height,depth,dim,data,filename);
      if (!filename) throw CImgArgumentException("CImg<%s>::save_medcon_external() : Instance image (%u,%u,%u,%u,%p), specified filename is (null).",
                                                 pixel_type(),width,height,depth,dim,data);
      char command[1024], filetmp[512], body[512];
      std::FILE *file;
      do {
        std::sprintf(filetmp,"%s.hdr",cimg::filenamerand());
        if ((file=std::fopen(filetmp,"rb"))!=0) std::fclose(file);
      } while (file);
      save_analyze(filetmp);
      std::sprintf(command,"%s -w -c dicom -o %s -f %s",cimg::medcon_path(),filename,filetmp);
      cimg::system(command);
      std::remove(filetmp);
      cimg::filename_split(filetmp,body);
      std::sprintf(filetmp,"%s.img",body);
      std::remove(filetmp);
      std::sprintf(command,"m000-%s",filename);
      file = std::fopen(command,"rb");
      if (!file) {
        cimg::fclose(cimg::fopen(filename,"r"));
        throw CImgIOException("CImg<%s>::save_medcon_external() : Failed to save image '%s'.\n\n"
                              "Path of 'medcon' : \"%s\"\n"
                              "Path of temporary filename : \"%s\"",
                              pixel_type(),filename,cimg::medcon_path(),filetmp);
      } else cimg::fclose(file);
      std::rename(command,filename);
      return *this;
    }

    //! Save the image as an ANALYZE7.5 or NIFTI file.
    const CImg<T>& save_analyze(const char *const filename, const float *const voxsize=0) const {
      if (is_empty()) throw CImgInstanceException("CImg<%s>::save_analyze() : File '%s', instance image (%u,%u,%u,%u,%p) is empty.",
                                                  pixel_type(),width,height,depth,dim,data,filename);
      if (!filename) throw CImgArgumentException("CImg<%s>::save_analyze() :  Instance image (%u,%u,%u,%u,%p), specified filename is (null).",
                                                 pixel_type(),width,height,depth,dim,data);
      std::FILE *file;
      char header[348], hname[1024], iname[1024];
      const char *ext = cimg::filename_split(filename);
      short datatype=-1;
      std::memset(header,0,348);
      if (!ext[0]) { std::sprintf(hname,"%s.hdr",filename); std::sprintf(iname,"%s.img",filename); }
      if (!cimg::strncasecmp(ext,"hdr",3)) {
        std::strcpy(hname,filename); std::strcpy(iname,filename); std::sprintf(iname+cimg::strlen(iname)-3,"img");
      }
      if (!cimg::strncasecmp(ext,"img",3)) {
        std::strcpy(hname,filename); std::strcpy(iname,filename); std::sprintf(hname+cimg::strlen(iname)-3,"hdr");
      }
      if (!cimg::strncasecmp(ext,"nii",3)) {
        std::strcpy(hname,filename); iname[0] = 0;
      }
      ((int*)(header))[0] = 348;
      std::sprintf(header+4,"CImg");
      std::sprintf(header+14," ");
      ((short*)(header+36))[0] = 4096;
      ((char*)(header+38))[0] = 114;
      ((short*)(header+40))[0] = 4;
      ((short*)(header+40))[1] = width;
      ((short*)(header+40))[2] = height;
      ((short*)(header+40))[3] = depth;
      ((short*)(header+40))[4] = dim;
      if (!cimg::strcasecmp(pixel_type(),"bool"))           datatype = 2;
      if (!cimg::strcasecmp(pixel_type(),"unsigned char"))  datatype = 2;
      if (!cimg::strcasecmp(pixel_type(),"char"))           datatype = 2;
      if (!cimg::strcasecmp(pixel_type(),"unsigned short")) datatype = 4;
      if (!cimg::strcasecmp(pixel_type(),"short"))          datatype = 4;
      if (!cimg::strcasecmp(pixel_type(),"unsigned int"))   datatype = 8;
      if (!cimg::strcasecmp(pixel_type(),"int"))            datatype = 8;
      if (!cimg::strcasecmp(pixel_type(),"unsigned long"))  datatype = 8;
      if (!cimg::strcasecmp(pixel_type(),"long"))           datatype = 8;
      if (!cimg::strcasecmp(pixel_type(),"float"))          datatype = 16;
      if (!cimg::strcasecmp(pixel_type(),"double"))         datatype = 64;
      if (datatype<0)
        throw CImgIOException("CImg<%s>::save_analyze() : Cannot save image '%s' since pixel type (%s)"
                              "is not handled in Analyze7.5 specifications.\n",
                              pixel_type(),filename,pixel_type());
      ((short*)(header+70))[0] = datatype;
      ((short*)(header+72))[0] = sizeof(T);
      ((float*)(header+112))[0] = 1;
      ((float*)(header+76))[0] = 0;
      if (voxsize) {
        ((float*)(header+76))[1] = voxsize[0];
        ((float*)(header+76))[2] = voxsize[1];
        ((float*)(header+76))[3] = voxsize[2];
      } else ((float*)(header+76))[1] = ((float*)(header+76))[2] = ((float*)(header+76))[3] = 1;
      file = cimg::fopen(hname,"wb");
      cimg::fwrite(header,348,file);
      if (iname[0]) { cimg::fclose(file); file = cimg::fopen(iname,"wb"); }
      cimg::fwrite(data,size(),file);
      cimg::fclose(file);
      return *this;
    }

    //! Save the image as a .cimg file.
    const CImg<T>& save_cimg(std::FILE *const file, const char *const filename=0) const {
      if (is_empty()) throw CImgInstanceException("CImg<%s>::save_cimg() : Instance image (%u,%u,%u,%u,%p) is empty (file '%s').",
                                                  pixel_type(),width,height,depth,dim,data,filename?filename:"(unknown)");
      if (!file && !filename) throw CImgArgumentException("CImg<%s>::save_cimg() : Instance image (%u,%u,%u,%u,%p), specified file is (null).",
                                                          pixel_type(),width,height,depth,dim,data);
      CImgList<T> tmp(1);
      tmp[0].width = width;
      tmp[0].height = height;
      tmp[0].depth = depth;
      tmp[0].dim = dim;
      tmp[0].data = data;
      tmp.save_cimg(file,filename);
      tmp[0].width = tmp[0].height = tmp[0].depth = tmp[0].dim = 0;
      tmp[0].data = 0;
      return *this;
    }

    //! Save the image as a .cimg file.
    const CImg<T>& save_cimg(const char *const filename) const {
      return save_cimg(0,filename);
    }

    //! 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 v0) const {
      CImgList<T> tmp(1);
      tmp[0].width = width;
      tmp[0].height = height;
      tmp[0].depth = depth;
      tmp[0].dim = dim;
      tmp[0].data = data;
      tmp.save_cimg(file,n0,x0,y0,z0,v0);
      tmp[0].width = tmp[0].height = tmp[0].depth = tmp[0].dim = 0;
      tmp[0].data = 0;
      return *this;
    }

    //! Insert the image into an existing .cimg file, at specified coordinates.
    const CImg<T>& save_cimg(const char *const filename,
                             const unsigned int n0,
                             const unsigned int x0, const unsigned int y0,
                             const unsigned int z0, const unsigned int v0) const {
      CImgList<T> tmp(1);
      tmp[0].width = width;
      tmp[0].height = height;
      tmp[0].depth = depth;
      tmp[0].dim = dim;
      tmp[0].data = data;
      tmp.save_cimg(filename,n0,x0,y0,z0,v0);
      tmp[0].width = tmp[0].height = tmp[0].depth = tmp[0].dim = 0;
      tmp[0].data = 0;
      return *this;
    }

    //! 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 dv=1) {
      return CImgList<T>::save_empty_cimg(file,1,dx,dy,dz,dv);
    }

    //! Save an empty .cimg file with specified dimensions.
    static void save_empty_cimg(const char *const filename,
                                const unsigned int dx, const unsigned int dy=1,
                                const unsigned int dz=1, const unsigned int dv=1) {
      return CImgList<T>::save_empty_cimg(filename,1,dx,dy,dz,dv);
    }

    //! Save the image as a RAW file
    const CImg<T>& save_raw(std::FILE *const file, const char *const filename=0, const bool multiplexed=false) const {
      if (is_empty()) throw CImgInstanceException("CImg<%s>::save_raw() : Instance image (%u,%u,%u,%u,%p) is empty (file '%s').",
                                                  pixel_type(),width,height,depth,dim,data,filename?filename:"(unknown)");
      if (!file && !filename) throw CImgArgumentException("CImg<%s>::save_raw() : Instance image (%u,%u,%u,%u,%p), specified file is (null).",
                                                          pixel_type(),width,height,depth,dim,data);
      std::FILE *const nfile = file?file:cimg::fopen(filename,"wb");
      if (!multiplexed) cimg::fwrite(data,size(),nfile);
      else {
        CImg<T> buf(dim);
        cimg_forXYZ(*this,x,y,z) {
          cimg_forV(*this,k) buf[k] = (*this)(x,y,z,k);
          cimg::fwrite(buf.data,dim,nfile);
        }
      }
      if (!file) cimg::fclose(nfile);
      return *this;
    }

    //! Save the image as a RAW file
    const CImg<T>& save_raw(const char *const filename=0, const bool multiplexed=false) const {
      return save_raw(0,filename,multiplexed);
    }

    //! Save the image using ImageMagick's convert.
    /** Function that saves the image for other file formats that are not natively handled by CImg,
        using the tool 'convert' from the ImageMagick package.\n
        This is the case for all compressed image formats (GIF,PNG,JPG,TIF, ...). You need to install
        the ImageMagick package in order to get
        this function working properly (see http://www.imagemagick.org ).
    **/
    const CImg<T>& save_imagemagick_external(const char *const filename, const unsigned int quality=100) const {
      if (is_empty()) throw CImgInstanceException("CImg<%s>::save_imagemagick_external() : Instance image (%u,%u,%u,%u,%p) is empty (file '%s')",
                                                  pixel_type(),width,height,depth,dim,data,filename);
      if (!filename) throw CImgArgumentException("CImg<%s>::save_imagemagick_external() : Instance image (%u,%u,%u,%u,%p), specified filename is (null).",
                                                 pixel_type(),width,height,depth,dim,data);
      char command[1024],filetmp[512];
      std::FILE *file;
      do {
        if (dim==1) std::sprintf(filetmp,"%s%s%s.pgm",cimg::temporary_path(),cimg_OS==2?"\\":"/",cimg::filenamerand());
        else std::sprintf(filetmp,"%s%s%s.ppm",cimg::temporary_path(),cimg_OS==2?"\\":"/",cimg::filenamerand());
        if ((file=std::fopen(filetmp,"rb"))!=0) std::fclose(file);
      } while (file);
      save_pnm(filetmp);
      std::sprintf(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<%s>::save_imagemagick_external() : Failed to save image '%s'.\n\n"
                                       "Path of 'convert' : \"%s\"\n"
                                       "Path of temporary filename : \"%s\"\n",
                                       pixel_type(),filename,cimg::imagemagick_path(),filetmp);
      if (file) cimg::fclose(file);
      std::remove(filetmp);
      return *this;
    }

    //! Save the image using GraphicsMagick's gm.
    /** Function that saves the image for other file formats that are not natively handled by CImg,
        using the tool 'gm' from the GraphicsMagick package.\n
        This is the case for all compressed image formats (GIF,PNG,JPG,TIF, ...). You need to install
        the GraphicsMagick package in order to get
        this function working properly (see http://www.graphicsmagick.org ).
    **/
    const CImg<T>& save_graphicsmagick_external(const char *const filename, const unsigned int quality=100) const {
      if (is_empty()) throw CImgInstanceException("CImg<%s>::save_graphicsmagick_external() : Instance image (%u,%u,%u,%u,%p) is empty (file '%s')",
                                                  pixel_type(),width,height,depth,dim,data,filename);
      if (!filename) throw CImgArgumentException("CImg<%s>::save_graphicsmagick_external() : Instance image (%u,%u,%u,%u,%p), specified filename is (null).",
                                                 pixel_type(),width,height,depth,dim,data);
      char command[1024],filetmp[512];
      std::FILE *file;
      do {
        if (dim==1) std::sprintf(filetmp,"%s%s%s.pgm",cimg::temporary_path(),cimg_OS==2?"\\":"/",cimg::filenamerand());
        else std::sprintf(filetmp,"%s%s%s.ppm",cimg::temporary_path(),cimg_OS==2?"\\":"/",cimg::filenamerand());
        if ((file=std::fopen(filetmp,"rb"))!=0) std::fclose(file);
      } while (file);
      save_pnm(filetmp);
      std::sprintf(command,"%s -quality %u%% %s \"%s\"",cimg::graphicsmagick_path(),quality,filetmp,filename);
      cimg::system(command);
      file = std::fopen(filename,"rb");
      if (!file) throw CImgIOException("CImg<%s>::save_graphicsmagick_external() : Failed to save image '%s'.\n\n"
                                       "Path of 'gm' : \"%s\"\n"
                                       "Path of temporary filename : \"%s\"\n",
                                       pixel_type(),filename,cimg::graphicsmagick_path(),filetmp);
      if (file) cimg::fclose(file);
      std::remove(filetmp);
      return *this;
    }

    const CImg<T>& save_other(const char *const filename, const unsigned int quality=100) const {
      const unsigned int odebug = cimg::exception_mode();
      bool is_saved = true;
      cimg::exception_mode() = 0;
      try { save_magick(filename); }
      catch (CImgException&) {
        try { save_imagemagick_external(filename,quality); }
        catch (CImgException&) {
          try { save_graphicsmagick_external(filename,quality); }
          catch (CImgException&) {
            is_saved = false;
          }
        }
      }
      cimg::exception_mode() = odebug;
      if (!is_saved) throw CImgIOException("CImg<%s>::save_other() : File '%s' cannot be saved.\n"
                                           "Check you have either the ImageMagick or GraphicsMagick package installed.",
                                           pixel_type(),filename);
      return *this;
    }

    //! Save the image as an INRIMAGE-4 file.
    const CImg<T>& save_inr(std::FILE *const file, const char *const filename=0, const float *const voxsize=0) const {
      if (is_empty()) throw CImgInstanceException("CImg<%s>::save_inr() : Instance image (%u,%u,%u,%u,%p) is empty (file '%s').",
                                                  pixel_type(),width,height,depth,dim,data,filename?filename:"(unknown)");
      if (!filename) throw CImgArgumentException("CImg<%s>::save_inr() : Instance image (%u,%u,%u,%u,%p), specified file is (null).",
                                                 pixel_type(),width,height,depth,dim,data);
      int inrpixsize=-1;
      const char *inrtype = "unsigned fixed\nPIXSIZE=8 bits\nSCALE=2**0";
      if (!cimg::strcasecmp(pixel_type(),"unsigned char"))  { inrtype = "unsigned fixed\nPIXSIZE=8 bits\nSCALE=2**0"; inrpixsize = 1; }
      if (!cimg::strcasecmp(pixel_type(),"char"))           { inrtype = "fixed\nPIXSIZE=8 bits\nSCALE=2**0"; inrpixsize = 1; }
      if (!cimg::strcasecmp(pixel_type(),"unsigned short")) { inrtype = "unsigned fixed\nPIXSIZE=16 bits\nSCALE=2**0";inrpixsize = 2; }
      if (!cimg::strcasecmp(pixel_type(),"short"))          { inrtype = "fixed\nPIXSIZE=16 bits\nSCALE=2**0"; inrpixsize = 2; }
      if (!cimg::strcasecmp(pixel_type(),"unsigned int"))   { inrtype = "unsigned fixed\nPIXSIZE=32 bits\nSCALE=2**0";inrpixsize = 4; }
      if (!cimg::strcasecmp(pixel_type(),"int"))            { inrtype = "fixed\nPIXSIZE=32 bits\nSCALE=2**0"; inrpixsize = 4; }
      if (!cimg::strcasecmp(pixel_type(),"float"))          { inrtype = "float\nPIXSIZE=32 bits"; inrpixsize = 4; }
      if (!cimg::strcasecmp(pixel_type(),"double"))         { inrtype = "float\nPIXSIZE=64 bits"; inrpixsize = 8; }
      if (inrpixsize<=0) throw CImgIOException("CImg<%s>::save_inr() : Don't know how to save images of '%s'",pixel_type(),pixel_type());
      std::FILE *const nfile = file?file:cimg::fopen(filename,"wb");
      char header[257];
      int err = std::sprintf(header,"#INRIMAGE-4#{\nXDIM=%u\nYDIM=%u\nZDIM=%u\nVDIM=%u\n",width,height,depth,dim);
      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::endian()?"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_forV(*this,k) cimg::fwrite(&((*this)(x,y,z,k)),1,nfile);
      if (!file) cimg::fclose(nfile);
      return *this;
    }

    //! Save the image as an INRIMAGE-4 file.
    const CImg<T>& save_inr(const char *const filename, const float *const voxsize=0) const {
      return save_inr(0,filename,voxsize);
    }

#define cimg_save_pandore_case(sy,sz,sv,stype,id) \
   if (!saved && (sy?(sy==height):true) && (sz?(sz==depth):true) && (sv?(sv==dim):true) && !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); \
      cimg::fwrite(dims,nbdims,nfile); \
      if (id==2 || id==5 || id==8 || id==16 || id==19 || id==22 || id==26 || id==30) { \
        unsigned char *buffer = new unsigned char[size()]; \
        const T *ptrs = data; \
        cimg_foroff(*this,off) *(buffer++) = (unsigned char)(*(ptrs++)); \
        buffer-=size(); \
        cimg::fwrite(buffer,size(),nfile); \
        delete[] buffer; \
      } \
      if (id==3 || id==6 || id==9 || id==17 || id==20 || id==23 || id==27 || id==31) { \
        unsigned long *buffer = new unsigned long[size()]; \
        const T *ptrs = data; \
        cimg_foroff(*this,off) *(buffer++) = (long)(*(ptrs++)); \
        buffer-=size(); \
        cimg::fwrite(buffer,size(),nfile); \
        delete[] buffer; \
      } \
      if (id==4 || id==7 || id==10 || id==18 || id==21 || id==25 || id==29 || id==33) { \
        float *buffer = new float[size()]; \
        const T *ptrs = data; \
        cimg_foroff(*this,off) *(buffer++) = (float)(*(ptrs++)); \
        buffer-=size(); \
        cimg::fwrite(buffer,size(),nfile); \
        delete[] buffer; \
      } \
      saved = true; \
    }

    unsigned int _save_pandore_header_length(unsigned int id, unsigned int *dims, const unsigned int colorspace=0) const {
      unsigned int nbdims = 0;
      if (id==2 || id==3 || id==4)    { dims[0] = 1;   dims[1] = width;  nbdims = 2; }
      if (id==5 || id==6 || id==7)    { dims[0] = 1;   dims[1] = height; dims[2] = width;  nbdims=3; }
      if (id==8 || id==9 || id==10)   { dims[0] = dim; dims[1] = depth;  dims[2] = height; dims[3] = width; nbdims = 4; }
      if (id==16 || id==17 || id==18) { dims[0] = 3;   dims[1] = height; dims[2] = width;  dims[3] = colorspace; nbdims = 4; }
      if (id==19 || id==20 || id==21) { dims[0] = 3;   dims[1] = depth;  dims[2] = height; dims[3] = width; dims[4] = colorspace; nbdims = 5; }
      if (id==22 || id==23 || id==25) { dims[0] = dim; dims[1] = width;  nbdims = 2; }
      if (id==26 || id==27 || id==29) { dims[0] = dim; dims[1] = height; dims[2] = width;  nbdims=3; }
      if (id==30 || id==31 || id==33) { dims[0] = dim; dims[1] = depth;  dims[2] = height; dims[3] = width; nbdims = 4; }
      return nbdims;
    }

    //! Save the image as a PANDORE-5 file.
    const CImg<T>& save_pandore(std::FILE *const file, const char *const filename=0, const unsigned int colorspace=0) const {
      if (is_empty()) throw CImgInstanceException("CImg<%s>::save_pandore() : Instance image (%u,%u,%u,%u,%p) is empty (file '%s').",
                                                  pixel_type(),width,height,depth,dim,data,filename?filename:"(unknown)");
      if (!file && !filename) throw CImgArgumentException("CImg<%s>::save_pandore() : Instance image (%u,%u,%u,%u,%p), specified file is (null).",
                                                          pixel_type(),width,height,depth,dim,data);
      std::FILE *const nfile = file?file:cimg::fopen(filename,"wb");
      unsigned char header[36] = { 'P','A','N','D','O','R','E','0','4',0,0,0,
                                   0,0,0,0,
                                   'C','I','m','g',0,0,0,0,0,
                                   'N','o',' ','d','a','t','e',0,0,0,
                                   0 };
      unsigned int nbdims,dims[5];
      bool saved=false;
      cimg_save_pandore_case(1,1,1,"unsigned char",2);
      cimg_save_pandore_case(1,1,1,"char",3);
      cimg_save_pandore_case(1,1,1,"short",3);
      cimg_save_pandore_case(1,1,1,"unsigned short",3);
      cimg_save_pandore_case(1,1,1,"unsigned int",3);
      cimg_save_pandore_case(1,1,1,"int",3);
      cimg_save_pandore_case(1,1,1,"unsigned long",4);
      cimg_save_pandore_case(1,1,1,"long",3);
      cimg_save_pandore_case(1,1,1,"float",4);
      cimg_save_pandore_case(1,1,1,"double",4);

      cimg_save_pandore_case(0,1,1,"unsigned char",5);
      cimg_save_pandore_case(0,1,1,"char",6);
      cimg_save_pandore_case(0,1,1,"short",6);
      cimg_save_pandore_case(0,1,1,"unsigned short",6);
      cimg_save_pandore_case(0,1,1,"unsigned int",6);
      cimg_save_pandore_case(0,1,1,"int",6);
      cimg_save_pandore_case(0,1,1,"unsigned long",7);
      cimg_save_pandore_case(0,1,1,"long",6);
      cimg_save_pandore_case(0,1,1,"float",7);
      cimg_save_pandore_case(0,1,1,"double",7);

      cimg_save_pandore_case(0,0,1,"unsigned char",8);
      cimg_save_pandore_case(0,0,1,"char",9);
      cimg_save_pandore_case(0,0,1,"short",9);
      cimg_save_pandore_case(0,0,1,"unsigned short",9);
      cimg_save_pandore_case(0,0,1,"unsigned int",9);
      cimg_save_pandore_case(0,0,1,"int",9);
      cimg_save_pandore_case(0,0,1,"unsigned long",10);
      cimg_save_pandore_case(0,0,1,"long",9);
      cimg_save_pandore_case(0,0,1,"float",10);
      cimg_save_pandore_case(0,0,1,"double",10);

      cimg_save_pandore_case(0,1,3,"unsigned char",16);
      cimg_save_pandore_case(0,1,3,"char",17);
      cimg_save_pandore_case(0,1,3,"short",17);
      cimg_save_pandore_case(0,1,3,"unsigned short",17);
      cimg_save_pandore_case(0,1,3,"unsigned int",17);
      cimg_save_pandore_case(0,1,3,"int",17);
      cimg_save_pandore_case(0,1,3,"unsigned long",18);
      cimg_save_pandore_case(0,1,3,"long",17);
      cimg_save_pandore_case(0,1,3,"float",18);
      cimg_save_pandore_case(0,1,3,"double",18);

      cimg_save_pandore_case(0,0,3,"unsigned char",19);
      cimg_save_pandore_case(0,0,3,"char",20);
      cimg_save_pandore_case(0,0,3,"short",20);
      cimg_save_pandore_case(0,0,3,"unsigned short",20);
      cimg_save_pandore_case(0,0,3,"unsigned int",20);
      cimg_save_pandore_case(0,0,3,"int",20);
      cimg_save_pandore_case(0,0,3,"unsigned long",21);
      cimg_save_pandore_case(0,0,3,"long",20);
      cimg_save_pandore_case(0,0,3,"float",21);
      cimg_save_pandore_case(0,0,3,"double",21);

      cimg_save_pandore_case(1,1,0,"unsigned char",22);
      cimg_save_pandore_case(1,1,0,"char",23);
      cimg_save_pandore_case(1,1,0,"short",23);
      cimg_save_pandore_case(1,1,0,"unsigned short",23);
      cimg_save_pandore_case(1,1,0,"unsigned int",23);
      cimg_save_pandore_case(1,1,0,"int",23);
      cimg_save_pandore_case(1,1,0,"unsigned long",25);
      cimg_save_pandore_case(1,1,0,"long",23);
      cimg_save_pandore_case(1,1,0,"float",25);
      cimg_save_pandore_case(1,1,0,"double",25);

      cimg_save_pandore_case(0,1,0,"unsigned char",26);
      cimg_save_pandore_case(0,1,0,"char",27);
      cimg_save_pandore_case(0,1,0,"short",27);
      cimg_save_pandore_case(0,1,0,"unsigned short",27);
      cimg_save_pandore_case(0,1,0,"unsigned int",27);
      cimg_save_pandore_case(0,1,0,"int",27);
      cimg_save_pandore_case(0,1,0,"unsigned long",29);
      cimg_save_pandore_case(0,1,0,"long",27);
      cimg_save_pandore_case(0,1,0,"float",29);
      cimg_save_pandore_case(0,1,0,"double",29);

      cimg_save_pandore_case(0,0,0,"unsigned char",30);
      cimg_save_pandore_case(0,0,0,"char",31);
      cimg_save_pandore_case(0,0,0,"short",31);
      cimg_save_pandore_case(0,0,0,"unsigned short",31);
      cimg_save_pandore_case(0,0,0,"unsigned int",31);
      cimg_save_pandore_case(0,0,0,"int",31);
      cimg_save_pandore_case(0,0,0,"unsigned long",33);
      cimg_save_pandore_case(0,0,0,"long",31);
      cimg_save_pandore_case(0,0,0,"float",33);
      cimg_save_pandore_case(0,0,0,"double",33);

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

    //! Save the image as a PANDORE-5 file.
    const CImg<T>& save_pandore(const char *const filename=0, const unsigned int colorspace=0) const {
      return save_pandore(0,filename,colorspace);
    }

    //! Save the image as a YUV video sequence file
    const CImg<T>& save_yuv(std::FILE *const file, const char *const filename=0, const bool rgb2yuv=true) const {
      get_split('z').save_yuv(file,filename,rgb2yuv);
      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 {
      return save_yuv(0,filename,rgb2yuv);
    }

    //! Save the image as a video sequence file, using the external tool 'ffmpeg'.
    const CImg<T>& save_ffmpeg_external(const char *const filename, const char *const codec="mpeg2video") const {
      get_split('z').save_ffmpeg_external(filename,codec);
      return *this;
    }

    //! Save the image as a BMP file
    const CImg<T>& save_bmp(std::FILE *const file, const char *const filename=0) const {
      if (is_empty()) throw CImgInstanceException("CImg<%s>::save_bmp() : Instance image (%u,%u,%u,%u,%p) is empty (file '%s').",
                                                  pixel_type(),width,height,depth,dim,data,filename?filename:"(unknown)");
      if (!file && !filename) throw CImgArgumentException("CImg<%s>::save_bmp() : Instance image (%u,%u,%u,%u,%p), specified file is (null).",
                                                          pixel_type(),width,height,depth,dim,data);
      if (depth>1)
        cimg::warn("CImg<%s>::save_bmp() : Instance image (%u,%u,%u,%u,%p) is volumetric. Only the first slice will be saved (file '%s').",
                   pixel_type(),width,height,depth,dim,data,filename?filename:"(unknown)");
      if (dim>3)
        cimg::warn("CImg<%s>::save_bmp() : Instance image (%u,%u,%u,%u,%p) is multispectral. Only the three first channels will be saved (file '%s').",
                   pixel_type(),width,height,depth,dim,data,filename?filename:"(unknown)");

      std::FILE *const nfile = file?file:cimg::fopen(filename,"wb");
      unsigned char header[54] = { 0 }, align_buf[4] = { 0 };
      const unsigned int
        align     = (4-(3*width)%4)%4,
        buf_size  = (3*width+align)*dimy(),
        file_size = 54+buf_size;
      header[0] = 'B'; header[1] = 'M';
      header[0x02] = file_size&0xFF;
      header[0x03] = (file_size>>8)&0xFF;
      header[0x04] = (file_size>>16)&0xFF;
      header[0x05] = (file_size>>24)&0xFF;
      header[0x0A] = 0x36;
      header[0x0E] = 0x28;
      header[0x12] = width&0xFF;
      header[0x13] = (width>>8)&0xFF;
      header[0x14] = (width>>16)&0xFF;
      header[0x15] = (width>>24)&0xFF;
      header[0x16] = height&0xFF;
      header[0x17] = (height>>8)&0xFF;
      header[0x18] = (height>>16)&0xFF;
      header[0x19] = (height>>24)&0xFF;
      header[0x1A] = 1;
      header[0x1B] = 0;
      header[0x1C] = 24;
      header[0x1D] = 0;
      header[0x22] = buf_size&0xFF;
      header[0x23] = (buf_size>>8)&0xFF;
      header[0x24] = (buf_size>>16)&0xFF;
      header[0x25] = (buf_size>>24)&0xFF;
      header[0x27] = 0x1;
      header[0x2B] = 0x1;
      cimg::fwrite(header,54,nfile);

      const T
        *pR = ptr(0,height-1,0,0),
        *pG = (dim>=2)?ptr(0,height-1,0,1):0,
        *pB = (dim>=3)?ptr(0,height-1,0,2):0;

      switch (dim) {
      case 1: {
        cimg_forY(*this,y) { cimg_forX(*this,x) {
          const unsigned char val = (unsigned char)*(pR++);
          std::fputc(val,nfile); std::fputc(val,nfile); std::fputc(val,nfile);
        }
        cimg::fwrite(align_buf,align,nfile);
        pR-=2*width;
        }} break;
      case 2: {
        cimg_forY(*this,y) { cimg_forX(*this,x) {
          std::fputc(0,nfile);
          std::fputc((unsigned char)(*(pG++)),nfile);
          std::fputc((unsigned char)(*(pR++)),nfile);
        }
        cimg::fwrite(align_buf,align,nfile);
        pR-=2*width; pG-=2*width;
        }} break;
      default: {
        cimg_forY(*this,y) { cimg_forX(*this,x) {
          std::fputc((unsigned char)(*(pB++)),nfile);
          std::fputc((unsigned char)(*(pG++)),nfile);
          std::fputc((unsigned char)(*(pR++)),nfile);
        }
        cimg::fwrite(align_buf,align,nfile);
        pR-=2*width; pG-=2*width; pB-=2*width;
        }} break;
      }
      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 an image to a PNG file.
    // Most of this function has been written by Eric Fausett
    /**
       \param filename = name of the png image file to save
       \return *this
       \note The png format specifies a variety of possible data formats.  Grey scale, Grey
       scale with Alpha, RGB color, RGB color with Alpha, and Palletized color are supported.
       Per channel bit depths of 1, 2, 4, 8, and 16 are natively supported. The
       type of file saved depends on the number of channels in the CImg file.  If there is 4 or more
       channels, the image will be saved as an RGB color with Alpha image using the bottom 4 channels.
       If there are 3 channels, the saved image will be an RGB color image.  If 2 channels then the
       image saved will be Grey scale with Alpha, and if 1 channel will be saved as a Grey scale
       image.
    **/
    const CImg<T>& save_png(std::FILE *const file, const char *const filename=0) const {
      if (is_empty()) throw CImgInstanceException("CImg<%s>::save_png() : Instance image (%u,%u,%u,%u,%p) is empty (file '%s').",
                                                  pixel_type(),width,height,depth,dim,data,filename?filename:"(unknown)");
      if (!filename) throw CImgArgumentException("CImg<%s>::save_png() : Instance image (%u,%u,%u,%u,%p), specified filename is (null).",
                                                 pixel_type(),width,height,depth,dim,data);
      if (depth>1)
        cimg::warn("CImg<%s>::save_png() : Instance image (%u,%u,%u,%u,%p) is volumetric. Only the first slice will be saved (file '%s').",
                 pixel_type(),width,height,depth,dim,data,filename?filename:"(unknown)");
#ifndef cimg_use_png
      if (!file) return save_other(filename);
      else throw CImgIOException("CImg<%s>::save_png() : Cannot save a PNG image in a *FILE output. You must use 'libpng' to do this instead.",
                                 pixel_type());
#else
      std::FILE *const nfile = file?file:cimg::fopen(filename,"wb");

      // Setup PNG structures for write
      png_voidp user_error_ptr = 0;
      png_error_ptr user_error_fn = 0, user_warning_fn = 0;
      png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING,user_error_ptr, user_error_fn, user_warning_fn);
      if(!png_ptr){
        if (!file) cimg::fclose(nfile);
        throw CImgIOException("CImg<%s>::save_png() : File '%s', error when initializing 'png_ptr' data structure.",
                              pixel_type(),filename?filename:"(unknown)");
      }
      png_infop info_ptr = png_create_info_struct(png_ptr);
      if(!info_ptr){
        png_destroy_write_struct(&png_ptr,(png_infopp)0);
        if (!file) cimg::fclose(nfile);
        throw CImgIOException("CImg<%s>::save_png() : File '%s', error when initializing 'info_ptr' data structure.",
                              pixel_type(),filename?filename:"(unknown)");
      }
      if (setjmp(png_jmpbuf(png_ptr))){
        png_destroy_write_struct(&png_ptr, &info_ptr);
        if (!file) cimg::fclose(nfile);
        throw CImgIOException("CImg<%s>::save_png() : File '%s', unknown fatal error.",
                              pixel_type(),filename?filename:"(unknown)");
      }
      png_init_io(png_ptr, nfile);
      png_uint_32 width = dimx();
      png_uint_32 height = dimy();
      float vmin, vmax = (float)maxmin(vmin);
      const int bit_depth = (vmin<0 || vmax>=256)?16:8;
      int color_type;
      switch (dimv()) {
      case 1: color_type = PNG_COLOR_TYPE_GRAY; break;
      case 2: color_type = PNG_COLOR_TYPE_GRAY_ALPHA; break;
      case 3: color_type = PNG_COLOR_TYPE_RGB; break;
      default: color_type = PNG_COLOR_TYPE_RGB_ALPHA; break;
      }
      const int interlace_type = PNG_INTERLACE_NONE;
      const int compression_type = PNG_COMPRESSION_TYPE_DEFAULT;
      const int filter_method = PNG_FILTER_TYPE_DEFAULT;
      png_set_IHDR(png_ptr, info_ptr, width, height, bit_depth, color_type, interlace_type,compression_type, filter_method);
      png_write_info(png_ptr, info_ptr);
      const int byte_depth = bit_depth>>3;
      const int numChan = dimv()>4?4:dimv();
      const int pixel_bit_depth_flag = numChan * (bit_depth-1);

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

      // Deallocate Image Write Memory
      cimg_forY(*this,n) delete[] imgData[n];
      delete[] imgData;
      if (!file) cimg::fclose(nfile);
      return *this;
#endif
    }

    //! Save a file in PNG format
    const CImg<T>& save_png(const char *const filename) const {
      return save_png(0,filename);
    }

#ifdef cimg_use_tiff
    // Save a plane into a tiff file
    const CImg<T>& _save_tiff(TIFF *tif, const unsigned int directory=0) const {
      if (is_empty() || !tif) return *this;
      const char *const filename = TIFFFileName(tif);
      uint32 rowsperstrip = (uint32)-1;
      uint16 spp = dim, bpp = sizeof(T)*8, photometric, compression = COMPRESSION_NONE;
      if (spp==3 || spp==4) photometric = PHOTOMETRIC_RGB;
      else photometric = PHOTOMETRIC_MINISBLACK;
      TIFFSetDirectory(tif,directory);
      TIFFSetField(tif,TIFFTAG_IMAGEWIDTH,width);
      TIFFSetField(tif,TIFFTAG_IMAGELENGTH,height);
      TIFFSetField(tif,TIFFTAG_ORIENTATION,ORIENTATION_TOPLEFT);
      TIFFSetField(tif,TIFFTAG_SAMPLESPERPIXEL,spp);
      if (cimg::type<T>::is_float()) TIFFSetField(tif,TIFFTAG_SAMPLEFORMAT,3);
      else if (cimg::type<T>::min()==0) TIFFSetField(tif,TIFFTAG_SAMPLEFORMAT,1);
      else TIFFSetField(tif,TIFFTAG_SAMPLEFORMAT,2);
      TIFFSetField(tif,TIFFTAG_BITSPERSAMPLE,bpp);
      TIFFSetField(tif,TIFFTAG_PLANARCONFIG,PLANARCONFIG_CONTIG);
      TIFFSetField(tif,TIFFTAG_PHOTOMETRIC,photometric);
      TIFFSetField(tif,TIFFTAG_COMPRESSION,compression);
      rowsperstrip = TIFFDefaultStripSize(tif,rowsperstrip);
      TIFFSetField(tif,TIFFTAG_ROWSPERSTRIP,rowsperstrip);
      TIFFSetField(tif,TIFFTAG_FILLORDER,FILLORDER_MSB2LSB);
      TIFFSetField(tif,TIFFTAG_SOFTWARE,"CImg");
      T *buf = (T*)_TIFFmalloc(TIFFStripSize(tif));
      if (buf){
	for (unsigned int row = 0; row<height; row+=rowsperstrip) {
	  uint32 nrow = (row+rowsperstrip>height?height-row:rowsperstrip);
	  tstrip_t strip = TIFFComputeStrip(tif,row,0);
          tsize_t i = 0;
          for (unsigned int rr = 0; rr<nrow; ++rr)
            for (unsigned int cc = 0; cc<width; ++cc)
              for (unsigned int vv = 0; vv<spp; ++vv)
                buf[i++] = (*this)(cc,row+rr,vv);
          if (TIFFWriteEncodedStrip(tif,strip,buf,i*sizeof(T))<0)
            throw CImgException("CImg<%s>::save_tiff() : File '%s', an error occure while writing a strip.",
                                pixel_type(),filename?filename:"(FILE*)");
	}
	_TIFFfree(buf);
      }
      TIFFWriteDirectory(tif);
      return (*this);
    }
#endif

    //! Save a file in TIFF format.
    const CImg<T>& save_tiff(const char *const filename) const {
      if (is_empty()) throw CImgInstanceException("CImg<%s>::save_tiff() : File '%s', instance image (%u,%u,%u,%u,%p) is empty.",
                                                  pixel_type(),filename,width,height,depth,dim,data);
      if (!filename) throw CImgArgumentException("CImg<%s>::save_tiff() : Specified filename is (null) for instance image (%u,%u,%u,%u,%p).",
                                                 pixel_type(),width,height,depth,dim,data);
#ifdef cimg_use_tiff
      TIFF *tif = TIFFOpen(filename,"w");
      if (tif) {
        cimg_forZ(*this,z) get_slice(z)._save_tiff(tif,z);
        TIFFClose(tif);
      } else throw CImgException("CImg<%s>::save_tiff() : File '%s', error while opening file stream for writing.",
                                 pixel_type(),filename);
#else
      return save_other(filename);
#endif
      return *this;
    }

    //! Save a file in JPEG format.
    const CImg<T>& save_jpeg(std::FILE *const file, const char *const filename=0, const unsigned int quality=100) const {
      if (is_empty()) throw CImgInstanceException("CImg<%s>::save_jpeg() : Instance image (%u,%u,%u,%u,%p) is empty (file '%s').",
                                                  pixel_type(),width,height,depth,dim,data,filename?filename:"(unknown)");
      if (!file && !filename) throw CImgArgumentException("CImg<%s>::save_jpeg() : Instance image (%u,%u,%u,%u,%p), specified filename is (null).",
                                                          pixel_type(),width,height,depth,dim,data);
      if (depth>1)
        cimg::warn("CImg<%s>::save_jpeg() : Instance image (%u,%u,%u,%u,%p) is volumetric. Only the first slice will be saved (file '%s').",
                   pixel_type(),width,height,depth,dim,data,filename?filename:"(unknown)");
#ifndef cimg_use_jpeg
      if (!file) return save_other(filename,quality);
      else throw CImgIOException("CImg<%s>::save_jpeg() : Cannot save a JPEG image in a *FILE output. Use libjpeg instead.",
                                 pixel_type());
#else

      // Fill pixel buffer
      unsigned char *buf;
      unsigned int dimbuf = 0;
      J_COLOR_SPACE colortype = JCS_RGB;
      switch (dim) {
      case 1: { // Greyscale images
        unsigned char *buf2 = buf = new unsigned char[width*height*(dimbuf=1)];
        colortype = JCS_GRAYSCALE;
        const T *ptr_g = data;
        cimg_forXY(*this,x,y) *(buf2++) = (unsigned char)*(ptr_g++);
      } break;
      case 2: { // RG images
        unsigned char *buf2 = buf = new unsigned char[width*height*(dimbuf=3)];
        const T *ptr_r = ptr(0,0,0,0), *ptr_g = ptr(0,0,0,1);
        colortype = JCS_RGB;
        cimg_forXY(*this,x,y) {
          *(buf2++) = (unsigned char)*(ptr_r++);
          *(buf2++) = (unsigned char)*(ptr_g++);
          *(buf2++) = 0;
        }
      } break;
      case 3: { // RGB images
        unsigned char *buf2 = buf = new unsigned char[width*height*(dimbuf=3)];
        const T *ptr_r = ptr(0,0,0,0), *ptr_g = ptr(0,0,0,1), *ptr_b = ptr(0,0,0,2);
        colortype = JCS_RGB;
        cimg_forXY(*this,x,y) {
          *(buf2++) = (unsigned char)*(ptr_r++);
          *(buf2++) = (unsigned char)*(ptr_g++);
          *(buf2++) = (unsigned char)*(ptr_b++);
        }
      } break;
      default: { // YCMYK images
        unsigned char *buf2 = buf = new unsigned char[width*height*(dimbuf=4)];
        const T *ptr_r = ptr(0,0,0,0), *ptr_g = ptr(0,0,0,1), *ptr_b = ptr(0,0,0,2), *ptr_a = ptr(0,0,0,3);
        colortype = JCS_CMYK;
        cimg_forXY(*this,x,y) {
          *(buf2++) = (unsigned char)*(ptr_r++);
          *(buf2++) = (unsigned char)*(ptr_g++);
          *(buf2++) = (unsigned char)*(ptr_b++);
          *(buf2++) = (unsigned char)*(ptr_a++);
        }
      } 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);

      const unsigned int row_stride = width*dimbuf;
      JSAMPROW row_pointer[1];
      while (cinfo.next_scanline < cinfo.image_height) {
        row_pointer[0] = &buf[cinfo.next_scanline*row_stride];
        jpeg_write_scanlines(&cinfo,row_pointer,1);
      }
      jpeg_finish_compress(&cinfo);

      delete[] buf;
      if (!file) cimg::fclose(nfile);
      jpeg_destroy_compress(&cinfo);
      return *this;
#endif
    }

    //! Save a file in JPEG format.
    const CImg<T>& save_jpeg(const char *const filename, const unsigned int quality=100) const {
      return save_jpeg(0,filename,quality);
    }

    //! Save the image using built-in ImageMagick++ library
    const CImg<T>& save_magick(const char *const filename) const {
      if (is_empty()) throw CImgInstanceException("CImg<%s>::save_magick() : Instance image (%u,%u,%u,%u,%p) is empty (file '%s').",
                                                  pixel_type(),width,height,depth,dim,data);
      if (!filename) throw CImgArgumentException("CImg<%s>::save_magick() : Instance image (%u,%u,%u,%u,%p), specified file is (null).",
                                                 pixel_type(),width,height,depth,dim,data);
#ifdef cimg_use_magick
      Magick::Image image(Magick::Geometry(width,height),"black");
      image.type(Magick::TrueColorType);
      const T
        *rdata = ptr(0,0,0,0),
        *gdata = dim>1?ptr(0,0,0,1):0,
        *bdata = dim>2?ptr(0,0,0,2):0;
      Magick::PixelPacket *pixels = image.getPixels(0,0,width,height);
      switch (dim) {
      case 1: // Scalar images
        for (unsigned int off = width*height; off; --off) {
          pixels->red = pixels->green = pixels->blue = Magick::Color::scaleDoubleToQuantum(*(rdata++)/255.0);
          ++pixels;
        }
        break;
      case 2: // RG images
        for (unsigned int off = width*height; off; --off) {
          pixels->red   = Magick::Color::scaleDoubleToQuantum(*(rdata++)/255.0);
          pixels->green = Magick::Color::scaleDoubleToQuantum(*(gdata++)/255.0);
          pixels->blue  = 0;
          ++pixels;
        }
        break;
      default: // RGB images
        for (unsigned int off = width*height; off; --off) {
          pixels->red   = Magick::Color::scaleDoubleToQuantum(*(rdata++)/255.0);
          pixels->green = Magick::Color::scaleDoubleToQuantum(*(gdata++)/255.0);
          pixels->blue  = Magick::Color::scaleDoubleToQuantum(*(bdata++)/255.0);
          ++pixels;
        }
        break;
      }
      image.syncPixels();
      image.write(filename);
#else
      throw CImgIOException("CImg<%s>::save_magick() : File '%s', Magick++ library has not been linked.",
                            pixel_type(),filename);
#endif
      return *this;
    }

    //! Save the image as a RGBA file
    const CImg<T>& save_rgba(std::FILE *const file, const char *const filename=0) const {
      if (is_empty()) throw CImgInstanceException("CImg<%s>::save_rgba() : Instance image (%u,%u,%u,%u,%p) is empty (file '%s').",
                                                  pixel_type(),width,height,depth,dim,data,filename?filename:"(unknown)");
      if (!file && !filename) throw CImgArgumentException("CImg<%s>::save_rgba() : Instance image (%u,%u,%u,%u,%p), specified file is (null).",
                                                          pixel_type(),width,height,depth,dim,data);
      if (dim!=4)
        cimg::warn("CImg<%s>::save_rgba() : Instance image (%u,%u,%u,%u,%p) has not exactly 4 channels (file '%s').",
                   pixel_type(),width,height,depth,dim,data,filename?filename:"(unknown)");
      std::FILE *const nfile = file?file:cimg::fopen(filename,"wb");
      const unsigned int wh = width*height;
      unsigned char *buffer = new unsigned char[4*wh], *nbuffer=buffer;
      const T
        *ptr1 = ptr(0,0,0,0),
        *ptr2 = dim>1?ptr(0,0,0,1):0,
        *ptr3 = dim>2?ptr(0,0,0,2):0,
        *ptr4 = dim>3?ptr(0,0,0,3):0;
      switch (dim) {
      case 1: { // Scalar images
        for (unsigned int k=0; k<wh; ++k) {
          const unsigned char val = (unsigned char)*(ptr1++);
          *(nbuffer++) = val;
          *(nbuffer++) = val;
          *(nbuffer++) = val;
          *(nbuffer++) = 255;
        }} break;
      case 2: { // RG images
        for (unsigned int k=0; k<wh; ++k) {
          *(nbuffer++) = (unsigned char)(*(ptr1++));
          *(nbuffer++) = (unsigned char)(*(ptr2++));
          *(nbuffer++) = 0;
          *(nbuffer++) = 255;
        }} break;
      case 3: { // RGB images
        for (unsigned int k=0; k<wh; ++k) {
          *(nbuffer++) = (unsigned char)(*(ptr1++));
          *(nbuffer++) = (unsigned char)(*(ptr2++));
          *(nbuffer++) = (unsigned char)(*(ptr3++));
          *(nbuffer++) = 255;
        }} break;
      default: { // RGBA images
        for (unsigned int k=0; k<wh; ++k) {
          *(nbuffer++) = (unsigned char)(*(ptr1++));
          *(nbuffer++) = (unsigned char)(*(ptr2++));
          *(nbuffer++) = (unsigned char)(*(ptr3++));
          *(nbuffer++) = (unsigned char)(*(ptr4++));
        }} break;
      }
      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 RGB file
    const CImg<T>& save_rgb(std::FILE *const file, const char *const filename=0) const {
      if (is_empty()) throw CImgInstanceException("CImg<%s>::save_rgb() : Instance image (%u,%u,%u,%u,%p) is empty (file '%s').",
                                                  pixel_type(),width,height,depth,dim,data,filename?filename:"(unknown)");
      if (!file && !filename) throw CImgArgumentException("CImg<%s>::save_rgb() : Instance image (%u,%u,%u,%u,%p), specified file is (null).",
                                                          pixel_type(),width,height,depth,dim,data);
      if (dim!=3)
        cimg::warn("CImg<%s>::save_rgb() : Instance image (%u,%u,%u,%u,%p) has not exactly 3 channels (file '%s').",
                   pixel_type(),width,height,depth,dim,data,filename?filename:"(unknown)");
      std::FILE *const nfile = file?file:cimg::fopen(filename,"wb");
      const unsigned int wh = width*height;
      unsigned char *buffer = new unsigned char[3*wh], *nbuffer=buffer;
      const T
        *ptr1 = ptr(0,0,0,0),
        *ptr2 = dim>1?ptr(0,0,0,1):0,
        *ptr3 = dim>2?ptr(0,0,0,2):0;
      switch (dim) {
      case 1: { // Scalar image
        for (unsigned int k=0; k<wh; ++k) {
          const unsigned char val = (unsigned char)*(ptr1++);
          *(nbuffer++) = val;
          *(nbuffer++) = val;
          *(nbuffer++) = val;
        }} break;
      case 2: { // RG image
        for (unsigned int k=0; k<wh; ++k) {
          *(nbuffer++) = (unsigned char)(*(ptr1++));
          *(nbuffer++) = (unsigned char)(*(ptr2++));
          *(nbuffer++) = 0;
        }} break;
      default: { // RGB image
        for (unsigned int k=0; k<wh; ++k) {
          *(nbuffer++) = (unsigned char)(*(ptr1++));
          *(nbuffer++) = (unsigned char)(*(ptr2++));
          *(nbuffer++) = (unsigned char)(*(ptr3++));
        }} break;
      }
      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 OFF files (GeomView 3D object files)
    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 bool invert_faces=false) const {
      if (is_empty()) throw CImgInstanceException("CImg<%s>::save_off() : Instance image (%u,%u,%u,%u,%p) is empty (file '%s').",
                                                  pixel_type(),width,height,depth,dim,data,filename?filename:"(unknown)");
      if (!file && !filename) throw CImgArgumentException("CImg<%s>::save_off() : Specified filename is (null).",pixel_type());
      if (height<3) return get_resize(-100,3,1,1,0).save_off(file,filename,primitives,colors,invert_faces);
      std::FILE *const nfile = file?file:cimg::fopen(filename,"w");
      std::fprintf(nfile,"OFF\n%u %u %u\n",width,primitives.size,3*primitives.size);
      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 unsigned int prim = primitives[l].size();
        const bool textured = (prim>4);
        const CImg<tc>& color = colors[l];
        const unsigned int s = textured?color.dimv():color.size();
        const float
          r = textured?(s>0?(float)(color.get_shared_channel(0).mean()/255.0f):1.0f):(s>0?(float)(color(0)/255.0f):1.0f),
          g = textured?(s>1?(float)(color.get_shared_channel(1).mean()/255.0f):r)   :(s>1?(float)(color(1)/255.0f):r),
          b = textured?(s>2?(float)(color.get_shared_channel(2).mean()/255.0f):r)   :(s>2?(float)(color(2)/255.0f):r);

        switch (prim) {
        case 1:
          std::fprintf(nfile,"1 %u %f %f %f\n",(unsigned int)primitives(l,0),r,g,b);
          break;
        case 2: case 6:
          std::fprintf(nfile,"2 %u %u %f %f %f\n",(unsigned int)primitives(l,0),(unsigned int)primitives(l,1),r,g,b);
          break;
        case 3: case 9:
          if (invert_faces)
            std::fprintf(nfile,"3 %u %u %u %f %f %f\n",(unsigned int)primitives(l,0),(unsigned int)primitives(l,1),(unsigned int)primitives(l,2),r,g,b);
          else
            std::fprintf(nfile,"3 %u %u %u %f %f %f\n",(unsigned int)primitives(l,0),(unsigned int)primitives(l,2),(unsigned int)primitives(l,1),r,g,b);
          break;
        case 4: case 12:
          if (invert_faces)
            std::fprintf(nfile,"4 %u %u %u %u %f %f %f\n",
                         (unsigned int)primitives(l,0),(unsigned int)primitives(l,1),(unsigned int)primitives(l,2),(unsigned int)primitives(l,3),r,g,b);
          else
            std::fprintf(nfile,"4 %u %u %u %u %f %f %f\n",
                         (unsigned int)primitives(l,0),(unsigned int)primitives(l,3),(unsigned int)primitives(l,2),(unsigned int)primitives(l,1),r,g,b);
          break;
        }
      }
      if (!file) cimg::fclose(nfile);
      return *this;
    }

    //! Save OFF files (GeomView 3D object files)
    template<typename tf, typename tc>
    const CImg<T>& save_off(const char *const filename,
                            const CImgList<tf>& primitives, const CImgList<tc>& colors, const bool invert_faces=false) const {
      return save_off(0,filename,primitives,colors,invert_faces);
    }

    //! Get a 40x38 color logo of a 'danger' item
    static CImg<T> get_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.ptr(0,0,0,0), *ptr2 = res.ptr(0,0,0,1), *ptr3 = res.ptr(0,0,0,2);
        for (unsigned int off = 0; off<res.width*res.height;) {
          const unsigned char n = *(ptrs++), r = *(ptrs++), g = *(ptrs++), b = *(ptrs++);
          for (unsigned int l=0; l<n; ++off, ++l) { *(ptr1++) = (T)r; *(ptr2++) = (T)g; *(ptr3++) = (T)b; }
        }
        first_time = false;
      }
      return res;
    }

  };


  /*
   #-----------------------------------------
   #
   #
   #
   # Definition of the CImgList<> structure
   #
   #
   #
   #------------------------------------------
   */

  //! Class representing list of images CImg<T>.
  template<typename T> struct CImgList {

    //! Size of the list (number of elements inside)
    unsigned int size;

    //! Allocation size of the list
    unsigned int allocsize;

    //! Pointer to the first list element
    CImg<T> *data;

    //! Define a CImgList<T>::iterator
    typedef CImg<T>* iterator;

    //! Define a CImgList<T>::const_iterator
    typedef const CImg<T>* const_iterator;

    //! Get value type
    typedef T value_type;

    //@}
    //---------------------------
    //
    //! \name Plugins
    //@{
    //---------------------------
#ifdef cimglist_plugin
#include cimglist_plugin
#endif
#ifdef cimglist_plugin1
#include cimglist_plugin1
#endif
#ifdef cimglist_plugin2
#include cimglist_plugin2
#endif
#ifdef cimglist_plugin3
#include cimglist_plugin3
#endif
#ifdef cimglist_plugin4
#include cimglist_plugin4
#endif
#ifdef cimglist_plugin5
#include cimglist_plugin5
#endif
#ifdef cimglist_plugin6
#include cimglist_plugin6
#endif
#ifdef cimglist_plugin7
#include cimglist_plugin7
#endif
#ifdef cimglist_plugin8
#include cimglist_plugin8
#endif
    //@}

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

    //! Destructor.
    ~CImgList() {
      if (data) delete[] data;
    }

    //! Default constructor.
    CImgList():
      size(0),allocsize(0),data(0) {}

    //! Construct an image list containing n empty images
    explicit CImgList(const unsigned int n):
      size(n) {
      data = new CImg<T>[allocsize = cimg::max(16UL,cimg::nearest_pow2(n))];
    }

    //! Default copy constructor.
    template<typename t> CImgList(const CImgList<t>& list):
      size(0),allocsize(0),data(0) {
      assign(list.size);
      cimglist_for(*this,l) data[l].assign(list[l],false);
    }

    CImgList(const CImgList<T>& list):
      size(0),allocsize(0),data(0) {
      assign(list.size);
      cimglist_for(*this,l) data[l].assign(list[l],list[l].is_shared);
    }

    //! Advanced copy constructor.
    template<typename t> CImgList(const CImgList<t>& list, const bool shared):
      size(0),allocsize(0),data(0) {
      assign(list.size);
      if (shared) throw CImgArgumentException("CImgList<%s>::CImgList() : Cannot construct a list instance with shared images from "
                                              "a CImgList<%s> (different pixel types).",pixel_type(),CImgList<t>::pixel_type());
      cimglist_for(*this,l) data[l].assign(list[l],false);
    }

    CImgList(const CImgList<T>& list, const bool shared):
      size(0),allocsize(0),data(0) {
      assign(list.size);
      cimglist_for(*this,l) data[l].assign(list[l],shared);
    }

    //! Construct an image list containing n images with specified size.
    CImgList(const unsigned int n, const unsigned int width, const unsigned int height=1,
             const unsigned int depth=1, const unsigned int dim=1):
      size(0),allocsize(0),data(0) {
      assign(n);
      cimglist_for(*this,l) data[l].assign(width,height,depth,dim);
    }

    //! Construct an image list containing n images with specified size, filled with specified value.
    CImgList(const unsigned int n, const unsigned int width, const unsigned int height,
             const unsigned int depth, const unsigned int dim, const T val):
      size(0),allocsize(0),data(0) {
      assign(n);
      cimglist_for(*this,l) data[l].assign(width,height,depth,dim,val);
    }

    //! Construct an image list containing n images with specified size and specified pixel values (int version).
    CImgList(const unsigned int n, const unsigned int width, const unsigned int height,
             const unsigned int depth, const unsigned int dim, const int val0, const int val1, ...):
      size(0),allocsize(0),data(0) {
      _CImgList_stdarg(int);
    }

    //! Construct an image list containing n images with specified size and specified pixel values (double version).
    CImgList(const unsigned int n, const unsigned int width, const unsigned int height,
             const unsigned int depth, const unsigned int dim, const double val0, const double val1, ...):
      size(0),allocsize(0),data(0) {
      _CImgList_stdarg(double);
    }

    //! Construct a list containing n copies of the image img.
    template<typename t> CImgList(const unsigned int n, const CImg<t>& img):
      size(0),allocsize(0),data(0) {
      assign(n);
      cimglist_for(*this,l) data[l].assign(img,img.is_shared);
    }

    //! Construct a list containing n copies of the image img, forcing the shared state.
    template<typename t> CImgList(const unsigned int n, const CImg<t>& img, const bool shared):
      size(0),allocsize(0),data(0) {
      assign(n);
      cimglist_for(*this,l) data[l].assign(img,shared);
    }

    //! Construct an image list from one image.
    template<typename t> explicit CImgList(const CImg<t>& img):
      size(0),allocsize(0),data(0) {
      assign(1);
      data[0].assign(img,img.is_shared);
    }

    //! Construct an image list from one image, forcing the shared state.
    template<typename t> explicit CImgList(const CImg<t>& img, const bool shared):
      size(0),allocsize(0),data(0) {
      assign(1);
      data[0].assign(img,shared);
    }

    //! Construct an image list from two images.
    template<typename t1, typename t2> CImgList(const CImg<t1>& img1, const CImg<t2>& img2):
      size(0),allocsize(0),data(0) {
      assign(2);
      data[0].assign(img1,img1.is_shared); data[1].assign(img2,img2.is_shared);
    }

    template<typename t1, typename t2> CImgList(const CImg<t1>& img1, const CImg<t2>& img2, const bool shared):
      size(0),allocsize(0),data(0) {
      assign(2);
      data[0].assign(img1,shared); data[1].assign(img2,shared);
    }

    //! Construct an image list from three images.
    template<typename t1, typename t2, typename t3> CImgList(const CImg<t1>& img1, const CImg<t2>& img2, const CImg<t3>& img3):
      size(0),allocsize(0),data(0) {
      assign(3);
      data[0].assign(img1,img1.is_shared); data[1].assign(img2,img2.is_shared); data[2].assign(img3,img3.is_shared);
    }

    template<typename t1, typename t2, typename t3> CImgList(const CImg<t1>& img1, const CImg<t2>& img2, const CImg<t3>& img3, const bool shared):
      size(0),allocsize(0),data(0) {
      assign(3);
      data[0].assign(img1,shared); data[1].assign(img2,shared); data[2].assign(img3,shared);
    }

   //! Construct an image list from four images.
    template<typename t1, typename t2, typename t3, typename t4>
    CImgList(const CImg<t1>& img1, const CImg<t2>& img2, const CImg<t3>& img3, const CImg<t4>& img4):
      size(0),allocsize(0),data(0) {
      assign(4);
      data[0].assign(img1,img1.is_shared); data[1].assign(img2,img2.is_shared); data[2].assign(img3,img3.is_shared); data[3].assign(img4,img4.is_shared);
    }

    template<typename t1, typename t2, typename t3, typename t4>
    CImgList(const CImg<t1>& img1, const CImg<t2>& img2, const CImg<t3>& img3, const CImg<t4>& img4, const bool shared):
      size(0),allocsize(0),data(0) {
      assign(4);
      data[0].assign(img1,shared); data[1].assign(img2,shared); data[2].assign(img3,shared); data[3].assign(img4,shared);
    }

    //! Construct an image list from five images.
    template<typename t1, typename t2, typename t3, typename t4, typename t5>
    CImgList(const CImg<t1>& img1, const CImg<t2>& img2, const CImg<t3>& img3, const CImg<t4>& img4,
             const CImg<t5>& img5):
      size(0),allocsize(0),data(0) {
      assign(5);
      data[0].assign(img1,img1.is_shared); data[1].assign(img2,img2.is_shared); data[2].assign(img3,img3.is_shared); data[3].assign(img4,img4.is_shared);
      data[4].assign(img5,img5.is_shared);
    }

    template<typename t1, typename t2, typename t3, typename t4, typename t5>
    CImgList(const CImg<t1>& img1, const CImg<t2>& img2, const CImg<t3>& img3, const CImg<t4>& img4,
             const CImg<t5>& img5, const bool shared):
      size(0),allocsize(0),data(0) {
      assign(5);
      data[0].assign(img1,shared); data[1].assign(img2,shared); data[2].assign(img3,shared); data[3].assign(img4,shared);
      data[4].assign(img5,shared);
    }

    //! Construct an image list from six images.
    template<typename t1, typename t2, typename t3, typename t4, typename t5, typename t6>
    CImgList(const CImg<t1>& img1, const CImg<t2>& img2, const CImg<t3>& img3, const CImg<t4>& img4,
             const CImg<t5>& img5, const CImg<t6>& img6):
      size(0),allocsize(0),data(0) {
      assign(6);
      data[0].assign(img1,img1.is_shared); data[1].assign(img2,img2.is_shared); data[2].assign(img3,img3.is_shared); data[3].assign(img4,img4.is_shared);
      data[4].assign(img5,img5.is_shared); data[5].assign(img6,img6.is_shared);
    }

    template<typename t1, typename t2, typename t3, typename t4, typename t5, typename t6>
    CImgList(const CImg<t1>& img1, const CImg<t2>& img2, const CImg<t3>& img3, const CImg<t4>& img4,
             const CImg<t5>& img5, const CImg<t6>& img6, const bool shared):
      size(0),allocsize(0),data(0) {
      assign(6);
      data[0].assign(img1,shared); data[1].assign(img2,shared); data[2].assign(img3,shared); data[3].assign(img4,shared);
      data[4].assign(img5,shared); data[5].assign(img6,shared);
    }

    //! Construct an image list from seven images.
    template<typename t1, typename t2, typename t3, typename t4, typename t5, typename t6, typename t7>
    CImgList(const CImg<t1>& img1, const CImg<t2>& img2, const CImg<t3>& img3, const CImg<t4>& img4,
             const CImg<t5>& img5, const CImg<t6>& img6, const CImg<t7>& img7):
      size(0),allocsize(0),data(0) {
      assign(7);
      data[0].assign(img1,img1.is_shared); data[1].assign(img2,img2.is_shared); data[2].assign(img3,img3.is_shared); data[3].assign(img4,img4.is_shared);
      data[4].assign(img5,img5.is_shared); data[5].assign(img6,img6.is_shared); data[6].assign(img7,img7.is_shared);
    }

    template<typename t1, typename t2, typename t3, typename t4, typename t5, typename t6, typename t7>
    CImgList(const CImg<t1>& img1, const CImg<t2>& img2, const CImg<t3>& img3, const CImg<t4>& img4,
             const CImg<t5>& img5, const CImg<t6>& img6, const CImg<t7>& img7, const bool shared):
      size(0),allocsize(0),data(0) {
      assign(7);
      data[0].assign(img1,shared); data[1].assign(img2,shared); data[2].assign(img3,shared); data[3].assign(img4,shared);
      data[4].assign(img5,shared); data[5].assign(img6,shared); data[6].assign(img7,shared);
    }

    //! Construct an image list from eight images.
    template<typename t1, typename t2, typename t3, typename t4, typename t5, typename t6, typename t7, typename t8>
    CImgList(const CImg<t1>& img1, const CImg<t2>& img2, const CImg<t3>& img3, const CImg<t4>& img4,
             const CImg<t5>& img5, const CImg<t6>& img6, const CImg<t7>& img7, const CImg<t8>& img8):
      size(0),allocsize(0),data(0) {
      assign(8);
      data[0].assign(img1,img1.is_shared); data[1].assign(img2,img2.is_shared); data[2].assign(img3,img3.is_shared); data[3].assign(img4,img4.is_shared);
      data[4].assign(img5,img5.is_shared); data[5].assign(img6,img6.is_shared); data[6].assign(img7,img7.is_shared); data[7].assign(img8,img8.is_shared);
    }

    template<typename t1, typename t2, typename t3, typename t4, typename t5, typename t6, typename t7, typename t8>
    CImgList(const CImg<t1>& img1, const CImg<t2>& img2, const CImg<t3>& img3, const CImg<t4>& img4,
             const CImg<t5>& img5, const CImg<t6>& img6, const CImg<t7>& img7, const CImg<t8>& img8, const bool shared):
      size(0),allocsize(0),data(0) {
      assign(8);
      data[0].assign(img1,shared); data[1].assign(img2,shared); data[2].assign(img3,shared); data[3].assign(img4,shared);
      data[4].assign(img5,shared); data[5].assign(img6,shared); data[6].assign(img7,shared); data[7].assign(img8,shared);
    }

   //! Construct an image list from a filename.
    CImgList(const char *const filename):
      size(0),allocsize(0),data(0) {
      assign(filename);
    }

    //! In-place version of the default constructor and default destructor
    CImgList<T>& assign() {
      if (data) delete[] data;
      size = allocsize = 0;
      data = 0;
      return *this;
    }

    //! Equivalent to assign() (STL-compliant name)
    CImgList<T>& clear() {
      return assign();
    }

    //! In-place version of the previous constructor
    CImgList<T>& assign(const unsigned int n) {
      if (n) {
        if (allocsize<n || allocsize>(n<<2)) {
          if (data) delete[] data;
          data = new CImg<T>[allocsize=cimg::max(16UL,cimg::nearest_pow2(n))];
        }
        size = n;
      } else assign();
      return *this;
    }

    //! In-place version of the previous constructor
    CImgList<T>& assign(const unsigned int n, const unsigned int width, const unsigned int height=1,
                        const unsigned int depth=1, const unsigned int dim=1) {
      assign(n);
      cimglist_for(*this,l) data[l].assign(width,height,depth,dim);
      return *this;
    }

    //! In-place version of the previous constructor
    CImgList<T>& assign(const unsigned int n, const unsigned int width, const unsigned int height,
                        const unsigned int depth, const unsigned int dim, const T val) {
      assign(n);
      cimglist_for(*this,l) data[l].assign(width,height,depth,dim,val);
      return *this;
    }

    //! In-place version of the previous constructor.
    CImgList<T>& assign(const unsigned int n, const unsigned int width, const unsigned int height,
                        const unsigned int depth, const unsigned int dim, const int val0, const int val1, ...) {
      _CImgList_stdarg(int);
      return *this;
    }

    //! In-place version of the previous constructor.
    CImgList<T>& assign(const unsigned int n, const unsigned int width, const unsigned int height,
                        const unsigned int depth, const unsigned int dim, const double val0, const double val1, ...) {
      _CImgList_stdarg(double);
      return *this;
    }

    //! In-place version of the copy constructor
    template<typename t> CImgList<T>& assign(const CImgList<t>& list) {
      assign(list.size);
      cimglist_for(*this,l) data[l].assign(list[l],list[l].is_shared);
      return *this;
    }

    //! In-place version of the copy constructor
    template<typename t> CImgList<T>& assign(const CImgList<t>& list, const bool shared) {
      assign(list.size);
      cimglist_for(*this,l) data[l].assign(list[l],shared);
      return *this;
    }

    //! In-place version of the previous constructor
    template<typename t> CImgList<T>& assign(const unsigned int n, const CImg<t>& img, const bool shared=false) {
      assign(n);
      cimglist_for(*this,l) data[l].assign(img,shared);
      return *this;
    }

    //! In-place version of the previous constructor
    template<typename t> CImgList<T>& assign(const CImg<t>& img, const bool shared=false) {
      assign(1);
      data[0].assign(img,shared);
      return *this;
    }

    //! In-place version of the previous constructor
    template<typename t1, typename t2>
    CImgList<T>& assign(const CImg<t1>& img1, const CImg<t2>& img2, const bool shared=false) {
      assign(2);
      data[0].assign(img1,shared); data[1].assign(img2,shared);
      return *this;
    }

    //! In-place version of the previous constructor
    template<typename t1, typename t2, typename t3>
    CImgList<T>& assign(const CImg<t1>& img1, const CImg<t2>& img2, const CImg<t3>& img3, const bool shared=false) {
      assign(3);
      data[0].assign(img1,shared); data[1].assign(img2,shared); data[2].assign(img3,shared);
      return *this;
    }

    //! In-place version of the previous constructor
    template<typename t1, typename t2, typename t3, typename t4>
    CImgList<T>& assign(const CImg<t1>& img1, const CImg<t2>& img2, const CImg<t3>& img3, const CImg<t4>& img4,
                        const bool shared=false) {
      assign(4);
      data[0].assign(img1,shared); data[1].assign(img2,shared); data[2].assign(img3,shared); data[3].assign(img4,shared);
      return *this;
    }

    //! In-place version of the previous constructor
    template<typename t1, typename t2, typename t3, typename t4, typename t5>
    CImgList<T>& assign(const CImg<t1>& img1, const CImg<t2>& img2, const CImg<t3>& img3, const CImg<t4>& img4,
                        const CImg<t5>& img5, const bool shared=false) {
      assign(5);
      data[0].assign(img1,shared); data[1].assign(img2,shared); data[2].assign(img3,shared); data[3].assign(img4,shared);
      data[4].assign(img5,shared);
      return *this;
    }

    //! In-place version of the previous constructor
    template<typename t1, typename t2, typename t3, typename t4, typename t5, typename t6>
    CImgList<T>& assign(const CImg<t1>& img1, const CImg<t2>& img2, const CImg<t3>& img3, const CImg<t4>& img4,
                        const CImg<t5>& img5, const CImg<t6>& img6, const bool shared=false) {
      assign(6);
      data[0].assign(img1,shared); data[1].assign(img2,shared); data[2].assign(img3,shared); data[3].assign(img4,shared);
      data[4].assign(img5,shared); data[5].assign(img6,shared);
      return *this;
    }

    //! In-place version of the previous constructor
    template<typename t1, typename t2, typename t3, typename t4, typename t5, typename t6, typename t7>
    CImgList<T>& assign(const CImg<t1>& img1, const CImg<t2>& img2, const CImg<t3>& img3, const CImg<t4>& img4,
                        const CImg<t5>& img5, const CImg<t6>& img6, const CImg<t7>& img7, const bool shared=false) {
      assign(7);
      data[0].assign(img1,shared); data[1].assign(img2,shared); data[2].assign(img3,shared); data[3].assign(img4,shared);
      data[4].assign(img5,shared); data[5].assign(img6,shared); data[6].assign(img7,shared);
      return *this;
    }

    //! In-place version of the previous constructor
    template<typename t1, typename t2, typename t3, typename t4, typename t5, typename t6, typename t7, typename t8>
    CImgList<T>& assign(const CImg<t1>& img1, const CImg<t2>& img2, const CImg<t3>& img3, const CImg<t4>& img4,
                        const CImg<t5>& img5, const CImg<t6>& img6, const CImg<t7>& img7, const CImg<t8>& img8, const bool shared=false) {
      assign(8);
      data[0].assign(img1,shared); data[1].assign(img2,shared); data[2].assign(img3,shared); data[3].assign(img4,shared);
      data[4].assign(img5,shared); data[5].assign(img6,shared); data[6].assign(img7,shared); data[7].assign(img8,shared);
      return *this;
    }

    //! In-place version of the previous constructor
    CImgList<T>& assign(const char *const filename) {
      return load(filename);
    }

    //! Transfer the content of the instance image list into another one.
    template<typename t> CImgList<T>& transfer_to(CImgList<t>& list) {
      list.assign(*this);
      assign();
      return list;
    }

    CImgList<T>& transfer_to(CImgList<T>& list) {
      list.assign();
      return swap(list);
    }

    //! Swap all data fields of two CImgList instances. Use with care !
    CImgList<T>& swap(CImgList<T>& list) {
      cimg::swap(size,list.size);
      cimg::swap(allocsize,list.allocsize);
      cimg::swap(data,list.data);
      return list;
    }

    //! Return a string describing the type of the image pixels in the list (template parameter \p T).
    static const char* pixel_type() {
      return cimg::type<T>::string();
    }

    //! Return \p true if list is empty
    bool is_empty() const {
      return (!data || !size);
    }

    operator bool() const {
      return !is_empty();
    }

    //! Return \c true if the list contains the pixel (n,x,y,z,v).
    bool containsNXYZV(const int n, const int x=0, const int y=0, const int z=0, const int v=0) const {
      if (is_empty()) return false;
      return n>=0 && n<size && x>=0 && x<data[n].dimx() && y>=0 && y<data[n].dimy() && z>=0 && z<data[n].dimz() && v>=0 && v<data[n].dimv();
    }

    //! Return \c true if the list contains the image (n).
    bool containsN(const int n) const {
      if (is_empty()) return false;
      return n>=0 && n<size;
    }

    //! Return \c true if one of the image list contains the pixel. If true, set coordinates (n,x,y,z,v).
    template<typename t> bool contains(const T& pixel, t& n, t& x, t&y, t& z, t& v) const {
      if (is_empty()) return false;
      cimglist_for(*this,l) if (data[l].contains(pixel,x,y,z,v)) { n = (t)l; return true; }
      return false;
    }

    //! Return \c true if one of the image list contains the pixel. If true, set coordinates (n,x,y,z).
    template<typename t> bool contains(const T& pixel, t& n, t& x, t&y, t& z) const {
      t v;
      return contains(pixel,n,x,y,z,v);
    }

    //! Return \c true if one of the image list contains the pixel. If true, set coordinates (n,x,y).
    template<typename t> bool contains(const T& pixel, t& n, t& x, t&y) const {
      t z,v;
      return contains(pixel,n,x,y,z,v);
    }

    //! Return \c true if one of the image list contains the pixel. If true, set coordinates (n,x).
    template<typename t> bool contains(const T& pixel, t& n, t& x) const {
      t y,z,v;
      return contains(pixel,n,x,y,z,v);
    }

    //! Return \c true if one of the image list contains the pixel. If true, set coordinates (n).
    template<typename t> bool contains(const T& pixel, t& n) const {
      t x,y,z,v;
      return contains(pixel,n,x,y,z,v);
    }

    //! Return \c true if one of the image list contains the pixel.
    bool contains(const T& pixel) const {
      unsigned int n,x,y,z,v;
      return contains(pixel,n,x,y,z,v);
    }

    //! Return \c true if the list contains the image 'img'. If true, returns the position (n) of the image in the list.
    template<typename t> bool contains(const CImg<T>& img, t& n) const {
      if (is_empty()) return false;
      const CImg<T> *const ptr = &img;
      cimglist_for(*this,i) if (data[i]==ptr) { n = (t)i; return true; }
      return false;
    }

    //! Return \c true if the list contains the image img.
    bool contains(const CImg<T>& img) const {
      unsigned int n;
      return contains(img,n);
    }

    //@}
    //------------------------------
    //
    //! \name Arithmetics Operators
    //@{
    //------------------------------

    //! Assignment operator
    template<typename t> CImgList<T>& operator=(const CImgList<t>& list) {
      return assign(list);
    }

    CImgList<T>& operator=(const CImgList<T>& list) {
      return assign(list);
    }

    //! Assignment operator.
    template<typename t> CImgList<T>& operator=(const CImg<t>& img) {
      cimglist_for(*this,l) data[l]=img;
      return *this;
    }

    //! Assignment operator.
    CImgList<T>& operator=(const T val) {
      cimglist_for(*this,l) data[l].fill(val);
      return *this;
    }

    //! Operator+
    CImgList<T> operator+() const {
      return CImgList<T>(*this);
    }

    //! Operator+=
#ifdef cimg_use_visualcpp6
    CImgList<T>& operator+=(const T val) {
#else
    template<typename t> CImgList<T>& operator+=(const t val) {
#endif
      cimglist_for(*this,l) (*this)[l]+=val;
      return *this;
    }

    //! Operator+=
    template<typename t> CImgList<T>& operator+=(const CImgList<t>& list) {
      const unsigned int sizemax = cimg::min(size,list.size);
      for (unsigned int l=0; l<sizemax; ++l) (*this)[l]+=list[l];
      return *this;
    }

    //! Operator++ (prefix)
    CImgList<T>& operator++() {
      cimglist_for(*this,l) ++(*this)[l];
      return *this;
    }

    //! Operator++ (postfix)
    CImgList<T> operator++(int) {
      CImgList<T> copy(*this);
      ++*this;
      return copy;
    }

    //! Operator-
    CImgList<T> operator-() const {
      CImgList<T> res(size);
      cimglist_for(res,l) res[l].assign(-data[l]);
      return res;
    }

    //! Operator-=.
#ifdef cimg_use_visualcpp6
    CImgList<T>& operator-=(const T val) {
#else
    template<typename t> CImgList<T>& operator-=(const t val) {
#endif
      cimglist_for(*this,l) (*this)[l]-=val;
      return *this;
    }

    //! Operator-=.
    template<typename t> CImgList<T>& operator-=(const CImgList<t>& list) {
      const unsigned int sizemax = min(size,list.size);
      for (unsigned int l=0; l<sizemax; ++l) (*this)[l]-=list[l];
      return *this;
    }

    //! Operator-- (prefix)
    CImgList<T>& operator--() {
      cimglist_for(*this,l) --(*this)[l];
      return *this;
    }

    //! Operator-- (postfix)
    CImgList<T> operator--(int) {
      CImgList<T> copy(*this);
      --*this;
      return copy;
    }

    //! Operator*=.
#ifdef cimg_use_visualcpp6
    CImgList<T>& operator*=(const double val) {
#else
    template<typename t> CImgList<T>& operator*=(const t val) {
#endif
      cimglist_for(*this,l) (*this)[l]*=val;
      return *this;
    }

    //! Operator*=.
    template<typename t> CImgList<T>& operator*=(const CImgList<t>& list) {
      const unsigned int N = cimg::min(size,list.size);
      for (unsigned int l=0; l<N; ++l) (*this)[l]*=list[l];
      return this;
    }

    //! Operator/=.
#ifdef cimg_use_visualcpp6
    CImgList<T>& operator/=(const double val) {
#else
    template<typename t> CImgList<T>& operator/=(const t val) {
#endif
      cimglist_for(*this,l) (*this)[l]/=val;
      return *this;
    }

    //! Operator/=.
    template<typename t> CImgList<T>& operator/=(const CImgList<t>& list) {
      const unsigned int N = cimg::min(size,list.size);
      for (unsigned int l=0; l<N; ++l) (*this)[l]/=list[l];
      return this;
    }

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

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

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

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

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

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

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

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

    //! Return the mean pixel value of the instance list.
    double mean() const {
      if (is_empty()) throw CImgInstanceException("CImgList<%s>::mean() : Instance image list is empty.",pixel_type());
      double val = 0;
      unsigned int siz = 0;
      cimglist_for(*this,l) {
        const CImg<T>& img = data[l];
        cimg_for(img,ptr,T) val+=(double)*ptr;
        siz+=img.size();
      }
      return val/siz;
    }

    //! Return the variance of the instance list.
    double variance() {
      if (is_empty())
        throw CImgInstanceException("CImgList<%s>::variance() : Instance image list is empty.",pixel_type());
      double res = 0;
      unsigned int siz = 0;
      double S = 0, S2 = 0;
      cimglist_for(*this,l) {
        const CImg<T>& img = data[l];
        cimg_for(img,ptr,T) { const double val = (double)*ptr; S+=val;  S2+=val*val; }
        siz+=img.size();
      }
      res = (S2 - S*S/siz)/siz;
      return res;
    }

    //@}
    //-------------------------
    //
    //! \name List Manipulation
    //@{
    //-------------------------

    //! Return a reference to the i-th element of the image list.
    CImg<T>& operator[](const unsigned int pos) {
#if cimg_debug>=3
      if (pos>=size) {
        cimg::warn("CImgList<%s>::operator[] : bad list position %u, in a list of %u images",pixel_type(),pos,size);
        return *data;
      }
#endif
      return data[pos];
    }

    const CImg<T>& operator[](const unsigned int pos) const {
#if cimg_debug>=3
      if (pos>=size) {
        cimg::warn("CImgList<%s>::operator[] : bad list position %u, in a list of %u images",pixel_type(),pos,size);
        return *data;
      }
#endif
      return data[pos];
    }

    //! Equivalent to CImgList<T>::operator[]
    CImg<T>& operator()(const unsigned int pos) {
      return (*this)[pos];
    }

    const CImg<T>& operator()(const unsigned int pos) const {
      return (*this)[pos];
    }

    //! Return a reference to (x,y,z,v) pixel of the pos-th image of the list
    T& operator()(const unsigned int pos, const unsigned int x, const unsigned int y=0,
                  const unsigned int z=0, const unsigned int v=0) {
      return (*this)[pos](x,y,z,v);
    }
    const T& operator()(const unsigned int pos, const unsigned int x, const unsigned int y=0,
                        const unsigned int z=0, const unsigned int v=0) const {
      return (*this)[pos](x,y,z,v);
    }

    //! Equivalent to CImgList<T>::operator[], with boundary checking
    CImg<T>& at(const unsigned int pos) {
      if (pos>=size)
        throw CImgArgumentException("CImgList<%s>::at() : bad list position %u, in a list of %u images",
                                    pixel_type(),pos,size);
      return data[pos];
    }

    const CImg<T>& at(const unsigned int pos) const {
      if (pos>=size)
        throw CImgArgumentException("CImgList<%s>::at() : bad list position %u, in a list of %u images",
                                    pixel_type(),pos,size);
      return data[pos];
    }

    //! Returns a reference to last element
    CImg<T>& back() {
      return (*this)(size-1);
    }

    const CImg<T>& back() const {
      return (*this)(size-1);
    }

    //! Returns a reference to the first element
    CImg<T>& front() {
      return *data;
    }

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

    //! Returns an iterator to the beginning of the vector.
    iterator begin() {
      return data;
    }

    const_iterator begin() const {
      return data;
    }

    //! Returns an iterator just past the last element.
    iterator end() {
      return data + size;
    }

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

    //! Insert a copy of the image \p img into the current image list, at position \p pos.
    template<typename t> CImgList<typename cimg::superset<T,t>::type>
      get_insert(const CImg<t>& img, const unsigned int pos=~0U, const bool shared=false) const {
      typedef typename cimg::superset<T,t>::type restype;
      return CImgList<restype>(*this).insert(img,pos,shared);
    }

    //! In-place version of the previous function.
    template<typename t> CImgList<T>& insert(const CImg<t>& img, const unsigned int pos, const bool shared) {
      const unsigned int npos = pos==~0U?size:pos;
      if (npos>size)
        throw CImgArgumentException("CImgList<%s>::insert() : Cannot insert at position %u into a list with %u elements",
                                    pixel_type(),npos,size);
      if (shared)
        throw CImgArgumentException("CImgList<%s>::insert(): Cannot insert a shared image CImg<%s> into a CImgList<%s>",
                                    pixel_type(),img.pixel_type(),pixel_type());
      CImg<T> *new_data = (++size>allocsize)?new CImg<T>[allocsize?(allocsize<<=1):(allocsize=16)]:0;
      if (!size || !data) {
        data = new_data;
        *data = img;
      } else {
        if (new_data) {
          if (npos) std::memcpy(new_data,data,sizeof(CImg<T>)*npos);
          if (npos!=size-1) std::memcpy(new_data+npos+1,data+npos,sizeof(CImg<T>)*(size-1-npos));
          std::memset(data,0,sizeof(CImg<T>)*(size-1));
          delete[] data;
          data = new_data;
        }
        else if (npos!=size-1) std::memmove(data+npos+1,data+npos,sizeof(CImg<T>)*(size-1-npos));
        data[npos].width = data[npos].height = data[npos].depth = data[npos].dim = 0; data[npos].data = 0;
        data[npos] = img;
      }
      return *this;
    }

    CImgList<T>& insert(const CImg<T>& img, const unsigned int pos, const bool shared) {
      const unsigned int npos = pos==~0U?size:pos;
      if (npos>size)
        throw CImgArgumentException("CImgList<%s>::insert() : Can't insert at position %u into a list with %u elements",
                                    pixel_type(),npos,size);
      CImg<T> *new_data = (++size>allocsize)?new CImg<T>[allocsize?(allocsize<<=1):(allocsize=16)]:0;
      if (!size || !data) {
        data = new_data;
        if (shared && img) {
          data->width = img.width; data->height = img.height; data->depth = img.depth; data->dim = img.dim;
          data->is_shared = true; data->data = img.data;
        } else *data = img;
      }
      else {
        if (new_data) {
          if (npos) std::memcpy(new_data,data,sizeof(CImg<T>)*npos);
          if (npos!=size-1) std::memcpy(new_data+npos+1,data+npos,sizeof(CImg<T>)*(size-1-npos));
          if (shared && img) {
            new_data[npos].width = img.width; new_data[npos].height = img.height; new_data[npos].depth = img.depth;
            new_data[npos].dim = img.dim; new_data[npos].is_shared = true; new_data[npos].data = img.data;
          } else {
            new_data[npos].width = new_data[npos].height = new_data[npos].depth = new_data[npos].dim = 0; new_data[npos].data = 0;
            new_data[npos] = img;
          }
          std::memset(data,0,sizeof(CImg<T>)*(size-1));
          delete[] data;
          data = new_data;
        } else {
          if (npos!=size-1) std::memmove(data+npos+1,data+npos,sizeof(CImg<T>)*(size-1-npos));
          if (shared && img) {
            data[npos].width = img.width; data[npos].height = img.height; data[npos].depth = img.depth; data[npos].dim = img.dim;
            data[npos].is_shared = true; data[npos].data = img.data;
          } else {
            data[npos].width = data[npos].height = data[npos].depth = data[npos].dim = 0; data[npos].data = 0;
            data[npos] = img;
          }
        }
      }
      return *this;
    }

    // The two functions below are necessary due to Visual C++ 6.0 function overloading bugs, when
    // default parameters are used in function signatures.
    template<typename t> CImgList<T>& insert(const CImg<t>& img, const unsigned int pos) {
      return insert(img,pos,false);
    }

    //! In-place version of the previous function.
    template<typename t> CImgList<T>& insert(const CImg<t>& img) {
      return insert(img,~0U,false);
    }

    //! Insert n empty images img into the current image list, at position \p pos.
    template<typename t> CImgList<typename cimg::superset<T,t>::type>
      get_insert(const unsigned int n, const unsigned int pos=~0U) const {
      typedef typename cimg::superset<T,t>::type restype;
      return CImgList<restype>(*this).insert(n,pos);
    }

    //! In-place version of the previous function.
    CImgList<T>& insert(const unsigned int n, const unsigned int pos=~0U) {
      CImg<T> foo;
      const unsigned int npos = pos==~0U?size:pos;
      for (unsigned int i=0; i<n; ++i) insert(foo,npos+i);
      return *this;
    }

    //! Insert n copies of the image \p img into the current image list, at position \p pos.
    template<typename t> CImgList<typename cimg::superset<T,t>::type>
      get_insert(const unsigned int n, const CImg<t>& img, const unsigned int pos=~0U, const bool shared=false) const {
      typedef typename cimg::superset<T,t>::type restype;
      return CImgList<restype>(*this).insert(n,img,pos,shared);
    }

    //! In-place version of the previous function.
    template<typename t> CImgList<T>& insert(const unsigned int n, const CImg<t>& img, const unsigned int pos=~0U, const bool shared=false) {
      const unsigned int npos = pos==~0U?size:pos;
      insert(img,npos,shared);
      for (unsigned int i=1; i<n; ++i) insert(data[npos],npos+i,shared);
      return *this;
    }

    //! Insert a copy of the image list \p list into the current image list, starting from position \p pos.
    template<typename t> CImgList<typename cimg::superset<T,t>::type>
      get_insert(const CImgList<t>& list, const unsigned int pos=~0U, const bool shared=false) const {
      typedef typename cimg::superset<T,t>::type restype;
      return CImgList<restype>(*this).insert(list,pos,shared);
    }

    //! In-place version of the previous function.
    template<typename t> CImgList<T>& insert(const CImgList<t>& list, const unsigned int pos=~0U, const bool shared=false) {
      const unsigned int npos = pos==~0U?size:pos;
      if ((void*)this!=(void*)&list) cimglist_for(list,l) insert(list[l],npos+l,shared);
      else insert(CImgList<T>(list),npos,shared);
      return *this;
    }

    //! Insert n copies of the list \p list at position \p pos of the current list.
    template<typename t> CImgList<typename cimg::superset<T,t>::type>
      get_insert(const unsigned int n, const CImgList<t>& list, const unsigned int pos=~0U, const bool shared=false) const {
      typedef typename cimg::superset<T,t>::type restype;
      return CImgList<restype>(*this).insert(n,list,pos,shared);
    }

    //! In-place version of the previous function.
    template<typename t> CImgList<T>& insert(const unsigned int n, const CImgList<t>& list, const unsigned int pos=~0U, const bool shared=false) {
      const unsigned int npos = pos==~0U?size:pos;
      for (unsigned int i=0; i<n; ++i) insert(list,npos,shared);
      return *this;
    }

    //! Remove the images at positions \p pos1 to \p pos2 from the image list.
    CImgList<T> get_remove(const unsigned int pos1, const unsigned int pos2) const {
      return (+*this).remove(pos1,pos2);
    }

    //! In-place version of the previous function.
    CImgList<T>& remove(const unsigned int pos1, const unsigned int pos2) {
      const unsigned int
        npos1 = pos1<pos2?pos1:pos2,
        tpos2 = pos1<pos2?pos2:pos1,
        npos2 = tpos2<size?tpos2:size-1;
      if (npos1>=size)
        cimg::warn("CImgList<%s>::remove() : Cannot remove images from a list (%p,%u), at positions %u->%u.",
                   pixel_type(),data,size,npos1,tpos2);
      else {
        if (tpos2>=size)
          cimg::warn("CImgList<%s>::remove() : Cannot remove all images from a list (%p,%u), at positions %u->%u.",
                     pixel_type(),data,size,npos1,tpos2);
        for (unsigned int k = npos1; k<=npos2; ++k) data[k].assign();
        const unsigned int nb = 1 + npos2 - npos1;
        if (!(size-=nb)) return assign();
        if (size>(allocsize>>2) || allocsize<=8) { // Removing items without reallocation.
          if (npos1!=size) std::memmove(data+npos1,data+npos2+1,sizeof(CImg<T>)*(size-npos1));
          std::memset(data+size,0,sizeof(CImg<T>)*nb);
        } else { // Removing items with reallocation.
          allocsize>>=2;
          while (allocsize>8 && size<(allocsize>>1)) allocsize>>=1;
          CImg<T> *new_data = new CImg<T>[allocsize];
          if (npos1) std::memcpy(new_data,data,sizeof(CImg<T>)*npos1);
          if (npos1!=size) std::memcpy(new_data+npos1,data+npos2+1,sizeof(CImg<T>)*(size-npos1));
          if (size!=allocsize) std::memset(new_data+size,0,sizeof(allocsize-size));
          std::memset(data,0,sizeof(CImg<T>)*(size+nb));
          delete[] data;
          data = new_data;
        }
      }
      return *this;
    }

    //! Remove the image at position \p pos from the image list.
    CImgList<T> get_remove(const unsigned int pos) const {
      return (+*this).remove(pos);
    }

    //! In-place version of the previous function.
    CImgList<T>& remove(const unsigned int pos) {
      return remove(pos,pos);
    }

    //! Remove the last image from the image list.
    CImgList<T> get_remove() const {
      return (+*this).remove();
    }

    //! In-place version of the previous function.
    CImgList<T>& remove() {
      if (size) return remove(size-1);
      else cimg::warn("CImgList<%s>::remove() : List is empty",pixel_type());
      return *this;
    }

    //! Reverse list order
    CImgList<T> get_reverse() const {
      return (+*this).reverse();
    }

    //! In-place version of the previous function.
    CImgList<T>& reverse() {
      for (unsigned int l=0; l<size/2; ++l) (*this)[l].swap((*this)[size-1-l]);
      return *this;
    }

    //! Get a sub-list
    CImgList<T> get_crop(const unsigned int i0, const unsigned int i1, const bool shared=false) const {
      if (i0>i1 || i1>=size)
        throw CImgArgumentException("CImgList<%s>::crop() : Cannot crop a sub-list (%u->%u) from a list (%u,%p)",
                                    pixel_type(),i0,i1,size,data);
      CImgList<T> res(i1-i0+1);
      cimglist_for(res,l) res[l].assign((*this)[i0+l],shared);
      return res;
    }

    //! In-place version of the previous function.
    CImgList<T>& crop(const unsigned int i0, const unsigned int i1, const bool shared=false) {
      return get_crop(i0,i1,shared).transfer_to(*this);
    }

    //! Get sub-images of a sublist
    CImgList<T> get_crop(const unsigned int i0, const unsigned int i1,
                         const int x0, const int y0, const int z0, const int v0,
                         const int x1, const int y1, const int z1, const int v1) const {
      if (i0>i1 || i1>=size)
        throw CImgArgumentException("CImgList<%s>::crop() : Cannot crop a sub-list (%u->%u) from a list (%u,%p)",
                                    pixel_type(),i0,i1,size,data);
      CImgList<T> res(i1-i0+1);
      cimglist_for(res,l) res[l] = (*this)[i0+l].get_crop(x0,y0,z0,v0,x1,y1,z1,v1);
      return res;
    }

    //! In-place version of the previous function.
    CImgList<T>& crop(const unsigned int i0, const unsigned int i1,
                      const int x0, const int y0, const int z0, const int v0,
                      const int x1, const int y1, const int z1, const int v1) {
      return get_crop(i0,i1,x0,y0,z0,v0,x1,y1,z1,v1).transfer_to(*this);
    }

    //! Get sub-images of a sublist
    CImgList<T> get_crop(const unsigned int i0, const unsigned int i1,
                         const int x0, const int y0, const int z0,
                         const int x1, const int y1, const int z1) const {
      if (i0>i1 || i1>=size)
        throw CImgArgumentException("CImgList<%s>::crop() : Cannot crop a sub-list (%u->%u) from a list (%u,%p)",
                                    pixel_type(),i0,i1,size,data);
      CImgList<T> res(i1-i0+1);
      cimglist_for(res,l) res[l] = (*this)[i0+l].get_crop(x0,y0,z0,x1,y1,z1);
      return res;
    }

    //! In-place version of the previous function.
    CImgList<T>& crop(const unsigned int i0, const unsigned int i1,
                      const int x0, const int y0, const int z0,
                      const int x1, const int y1, const int z1) {
      return get_crop(i0,i1,x0,y0,z0,x1,y1,z1).transfer_to(*this);
    }

    //! Get sub-images of a sublist
    CImgList<T> get_crop(const unsigned int i0, const unsigned int i1,
                         const int x0, const int y0,
                         const int x1, const int y1) const {
      if (i0>i1 || i1>=size)
        throw CImgArgumentException("CImgList<%s>::crop() : Cannot crop a sub-list (%u->%u) from a list (%u,%p)",
                                    pixel_type(),i0,i1,size,data);
      CImgList<T> res(i1-i0+1);
      cimglist_for(res,l) res[l] = (*this)[i0+l].get_crop(x0,y0,x1,y1);
      return res;
    }

    //! In-place version of the previous function.
    CImgList<T>& crop(const unsigned int i0, const unsigned int i1,
                      const int x0, const int y0,
                      const int x1, const int y1) {
      return get_crop(i0,i1,x0,y0,x1,y1).transfer_to(*this);
    }

    //! Get sub-images of a sublist
    CImgList<T> get_crop(const unsigned int i0, const unsigned int i1,
                         const int x0, const int x1) const {
      if (i0>i1 || i1>=size)
        throw CImgArgumentException("CImgList<%s>::crop() : Cannot crop a sub-list (%u->%u) from a list (%u,%p)",
                                    pixel_type(),i0,i1,size,data);
      CImgList<T> res(i1-i0+1);
      cimglist_for(res,l) res[l] = (*this)[i0+l].get_crop(x0,x1);
      return res;
    }

    //! In-place version of the previous function.
    CImgList<T>& crop(const unsigned int i0, const unsigned int i1,
                      const int x0, const int x1) {
      return get_crop(i0,i1,x0,x1).transfer_to(*this);
    }

   //! Insert a copy of the image \p img at the end of the current image list.
    template<typename t> CImgList<T>& operator<<(const CImg<t>& img) {
      return insert(img);
    }

    //! Insert a copy of the image list \p list at the end of the current image list.
    template<typename t> CImgList<T>& operator<<(const CImgList<t>& list) {
      return insert(list);
    }

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

    //! Insert a copy of the current image list at the beginning of the image list \p list.
    template<typename t> CImgList<T>& operator>>(CImgList<t>& list) const {
      return list.insert(*this,0);
    }

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

    //! Insert image \p img at the end of the list.
    template<typename t> CImgList<T>& push_back(const CImg<t>& img) {
      return insert(img);
    }

    //! Insert image \p img at the front of the list.
    template<typename t> CImgList<T>& push_front(const CImg<t>& img) {
      return insert(img,0);
    }

    //! Insert list \p list at the end of the current list.
    template<typename t> CImgList<T>& push_back(const CImgList<t>& list) {
      return insert(list);
    }

    //! Insert list \p list at the front of the current list.
    template<typename t> CImgList<T>& push_front(const CImgList<t>& list) {
      return insert(list,0);
    }

    //! Remove last element of the list;
    CImgList<T>& pop_back() {
      return remove(size-1);
    }

    //! Remove first element of the list;
    CImgList<T>& pop_front() {
      return remove(0);
    }

    //! Remove the element pointed by iterator \p iter;
    CImgList<T>& erase(const iterator iter) {
      return remove(iter-data);
    }

    //@}
    //----------------------------
    //
    //! \name Fourier Transforms
    //@{
    //----------------------------

    //! Compute the Fast Fourier Transform (along the specified axis).
    CImgList<typename cimg::superset<T,float>::type> get_FFT(const char axe, const bool invert=false) const {
      typedef typename cimg::superset<T,float>::type restype;
      return CImgList<restype>(*this).FFT(axe,invert);
    }

    //! In-place version of the previous function.
    CImgList<T>& FFT(const char axe, const bool invert=false) {
      if (is_empty()) throw CImgInstanceException("CImgList<%s>::FFT() : Instance list (%u,%p) is empty",pixel_type(),size,data);
      if (!data[0]) throw CImgInstanceException("CImgList<%s>::FFT() : Real part (%u,%u,%u,%u,%p) is empty",
                                                pixel_type(),data[0].width,data[0].height,data[0].depth,data[0].dim,data[0].data);
      if (size>2) cimg::warn("CImgList<%s>::FFT() : Instance list (%u,%p) have more than 2 images",pixel_type(),size,data);
      if (size==1) insert(CImg<T>(data[0].width,data[0].height,data[0].depth,data[0].dim,0));
      CImg<T> &Ir = data[0], &Ii = data[1];
      if (Ir.width!=Ii.width || Ir.height!=Ii.height || Ir.depth!=Ii.depth || Ir.dim!=Ii.dim)
        throw CImgInstanceException("CImgList<%s>::FFT() : Real part (%u,%u,%u,%u,%p) and imaginary part (%u,%u,%u,%u,%p)"
                                    "have different dimensions",pixel_type(),
                                    Ir.width,Ir.height,Ir.depth,Ir.dim,Ir.data,Ii.width,Ii.height,Ii.depth,Ii.dim,Ii.data);

#ifdef cimg_use_fftw3
      fftw_complex *data_in;
      fftw_plan data_plan;

      switch (cimg::uncase(axe)) {
      case 'x': {
        data_in = (fftw_complex*)fftw_malloc(sizeof(fftw_complex) * Ir.width);
        data_plan = fftw_plan_dft_1d(Ir.width, data_in, data_in, invert?FFTW_BACKWARD:FFTW_FORWARD, FFTW_ESTIMATE);
        cimg_forYZV(Ir,y,z,k) {
          T *ptrr = Ir.ptr(0,y,z,k), *ptri = Ii.ptr(0,y,z,k);
          double *ptrd = (double*)data_in;
          cimg_forX(Ir,x) { *(ptrd++) = (double)*(ptrr++); *(ptrd++) = (double)*(ptri++); }
          fftw_execute(data_plan);
          { cimg_forX(Ir,x) { *(--ptri) = (T)*(--ptrd); *(--ptrr) = (T)*(--ptrd); }}
        }
      } break;

      case 'y': {
        data_in = (fftw_complex*)fftw_malloc(sizeof(fftw_complex) * Ir.height);
        data_plan = fftw_plan_dft_1d(Ir.height, data_in, data_in, invert?FFTW_BACKWARD:FFTW_FORWARD, FFTW_ESTIMATE);
        const unsigned int off = Ir.width;
        cimg_forXZV(Ir,x,z,k) {
          T *ptrr = Ir.ptr(x,0,z,k), *ptri = Ii.ptr(x,0,z,k);
          double *ptrd = (double*)data_in;
          cimg_forY(Ir,y) { *(ptrd++) = (double)*ptrr; *(ptrd++) = (double)*ptri; ptrr+=off; ptri+=off; }
          fftw_execute(data_plan);
          { cimg_forY(Ir,y) { ptrr-=off; ptri-=off; *ptri = (T)*(--ptrd); *ptrr = (T)*(--ptrd); }}
        }
      } break;

      case 'z': {
        data_in = (fftw_complex*)fftw_malloc(sizeof(fftw_complex) * Ir.depth);
        data_plan = fftw_plan_dft_1d(Ir.depth, data_in, data_in, invert?FFTW_BACKWARD:FFTW_FORWARD, FFTW_ESTIMATE);
        const unsigned int off = Ir.width*Ir.height;
        cimg_forXYV(Ir,x,y,k) {
          T *ptrr = Ir.ptr(x,y,0,k), *ptri = Ii.ptr(x,y,0,k);
          double *ptrd = (double*)data_in;
          cimg_forZ(Ir,z) { *(ptrd++) = (double)*ptrr; *(ptrd++) = (double)*ptri; ptrr+=off; ptri+=off; }
          fftw_execute(data_plan);
          { cimg_forZ(Ir,z) { ptrr-=off; ptri-=off; *ptri = (T)*(--ptrd); *ptrr = (T)*(--ptrd); }}
        }
      } break;

      case 'v': {
        data_in = (fftw_complex*)fftw_malloc(sizeof(fftw_complex) * Ir.dim);
        data_plan = fftw_plan_dft_1d(Ir.dim, data_in, data_in, invert?FFTW_BACKWARD:FFTW_FORWARD, FFTW_ESTIMATE);
        const unsigned int off = Ir.width*Ir.height*Ir.depth;
        cimg_forXYZ(Ir,x,y,z) {
          T *ptrr = Ir.ptr(x,y,z,0), *ptri = Ii.ptr(x,y,z,0);
          double *ptrd = (double*)data_in;
          cimg_forV(Ir,k) { *(ptrd++) = (double)*ptrr; *(ptrd++) = (double)*ptri; ptrr+=off; ptri+=off; }
          fftw_execute(data_plan);
          { cimg_forV(Ir,k) { ptrr-=off; ptri-=off; *ptri = (T)*(--ptrd); *ptrr = (T)*(--ptrd); }}
        }
      } break;
      }

      fftw_destroy_plan(data_plan);
      fftw_free(data_in);
#else
      switch (cimg::uncase(axe)) {
      case 'x': { // Fourier along X
        const unsigned int N = Ir.width, N2 = (N>>1);
        if (((N-1)&N) && N!=1) throw CImgInstanceException("CImgList<%s>::FFT() : Dimension of instance image along 'x' is %d != 2^N",
                                                           pixel_type(),N);
        for (unsigned int i=0, j=0; i<N2; ++i) {
          if (j>i) cimg_forYZV(Ir,y,z,v) { cimg::swap(Ir(i,y,z,v),Ir(j,y,z,v)); cimg::swap(Ii(i,y,z,v),Ii(j,y,z,v));
          if (j<N2) {
            const unsigned int ri = N-1-i, rj = N-1-j;
            cimg::swap(Ir(ri,y,z,v),Ir(rj,y,z,v)); cimg::swap(Ii(ri,y,z,v),Ii(rj,y,z,v));
          }}
          for (unsigned int m=N, n=N2; (j+=n)>=m; j-=m, m=n, n>>=1) {}
        }
        for (unsigned int delta=2; delta<=N; delta<<=1) {
          const unsigned int delta2 = (delta>>1);
          for (unsigned int i=0; i<N; i+=delta) {
            float wr = 1, wi = 0;
            const float angle = (float)((invert?+1:-1)*2*cimg::valuePI/delta),
                        ca = (float)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_forYZV(Ir,y,z,k) {
                T &ir = Ir(j,y,z,k), &ii = Ii(j,y,z,k), &nir = Ir(nj,y,z,k), &nii = Ii(nj,y,z,k);
                const float tmpr = (float)(wr*nir - wi*nii), tmpi = (float)(wr*nii + wi*nir);
                nir = (T)(ir - tmpr);
                nii = (T)(ii - tmpi);
                ir += (T)tmpr;
                ii += (T)tmpi;
              }
              const float nwr = wr*ca-wi*sa;
              wi = wi*ca + wr*sa;
              wr = nwr;
            }
          }
        }
        if (invert) (*this)/=N;
      } break;

      case 'y': { // Fourier along Y
        const unsigned int N = Ir.height, N2 = (N>>1);
        if (((N-1)&N) && N!=1) throw CImgInstanceException("CImgList<%s>::FFT() : Dimension of instance image(s) along 'y' is %d != 2^N",
                                                           pixel_type(),N);
        for (unsigned int i=0, j=0; i<N2; ++i) {
          if (j>i) cimg_forXZV(Ir,x,z,v) { cimg::swap(Ir(x,i,z,v),Ir(x,j,z,v)); cimg::swap(Ii(x,i,z,v),Ii(x,j,z,v));
          if (j<N2) {
            const unsigned int ri = N-1-i, rj = N-1-j;
            cimg::swap(Ir(x,ri,z,v),Ir(x,rj,z,v)); cimg::swap(Ii(x,ri,z,v),Ii(x,rj,z,v));
          }}
          for (unsigned int m=N, n=N2; (j+=n)>=m; j-=m, m=n, n>>=1) {}
        }
        for (unsigned int delta=2; delta<=N; delta<<=1) {
          const unsigned int delta2 = (delta>>1);
          for (unsigned int i=0; i<N; i+=delta) {
            float wr = 1, wi = 0;
            const float angle = (float)((invert?+1:-1)*2*cimg::valuePI/delta),
                        ca = (float)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_forXZV(Ir,x,z,k) {
                T &ir = Ir(x,j,z,k), &ii = Ii(x,j,z,k), &nir = Ir(x,nj,z,k), &nii = Ii(x,nj,z,k);
                const float tmpr = (float)(wr*nir - wi*nii), tmpi = (float)(wr*nii + wi*nir);
                nir = (T)(ir - tmpr);
                nii = (T)(ii - tmpi);
                ir += (T)tmpr;
                ii += (T)tmpi;
              }
              const float nwr = wr*ca-wi*sa;
              wi = wi*ca + wr*sa;
              wr = nwr;
            }
          }
        }
        if (invert) (*this)/=N;
      } break;

      case 'z': { // Fourier along Z
        const unsigned int N = Ir.depth, N2 = (N>>1);
        if (((N-1)&N) && N!=1) throw CImgInstanceException("CImgList<%s>::FFT() : Dimension of instance image(s) along 'z' is %d != 2^N",
                                                           pixel_type(),N);
        for (unsigned int i=0, j=0; i<N2; ++i) {
          if (j>i) cimg_forXYV(Ir,x,y,v) { cimg::swap(Ir(x,y,i,v),Ir(x,y,j,v)); cimg::swap(Ii(x,y,i,v),Ii(x,y,j,v));
          if (j<N2) {
            const unsigned int ri = N-1-i, rj = N-1-j;
            cimg::swap(Ir(x,y,ri,v),Ir(x,y,rj,v)); cimg::swap(Ii(x,y,ri,v),Ii(x,y,rj,v));
          }}
          for (unsigned int m=N, n=N2; (j+=n)>=m; j-=m, m=n, n>>=1) {}
        }
        for (unsigned int delta=2; delta<=N; delta<<=1) {
          const unsigned int delta2 = (delta>>1);
          for (unsigned int i=0; i<N; i+=delta) {
            float wr = 1, wi = 0;
            const float angle = (float)((invert?+1:-1)*2*cimg::valuePI/delta),
                        ca = (float)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_forXYV(Ir,x,y,k) {
                T &ir = Ir(x,y,j,k), &ii = Ii(x,y,j,k), &nir = Ir(x,y,nj,k), &nii = Ii(x,y,nj,k);
                const float tmpr = (float)(wr*nir - wi*nii), tmpi = (float)(wr*nii + wi*nir);
                nir = (T)(ir - tmpr);
                nii = (T)(ii - tmpi);
                ir += (T)tmpr;
                ii += (T)tmpi;
              }
              const float nwr = wr*ca-wi*sa;
              wi = wi*ca + wr*sa;
              wr = nwr;
            }
          }
        }
        if (invert) (*this)/=N;
      } break;

      default: throw CImgArgumentException("CImgList<%s>::FFT() : unknown axe '%c', must be 'x','y' or 'z'");
      }
#endif
      return *this;
    }

    //! Compute the Fast Fourier Transform of a complex image.
    CImgList<typename cimg::superset<T,float>::type> get_FFT(const bool invert=false) const {
      typedef typename cimg::superset<T,float>::type restype;
      return CImgList<restype>(*this).FFT(invert);
    }

    //! In-place version of the previous function.
    CImgList<T>& FFT(const bool invert=false) {
      if (is_empty()) throw CImgInstanceException("CImgList<%s>::FFT() : Instance list (%u,%p) is empty",pixel_type(),size,data);
      if (size>2) cimg::warn("CImgList<%s>::FFT() : Instance list (%u,%p) have more than 2 images",pixel_type(),size,data);
      if (size==1) insert(CImg<T>(data->width,data->height,data->depth,data->dim,0));
      CImg<T> &Ir = data[0], &Ii = data[1];
      if (Ii.width!=Ir.width || Ii.height!=Ir.height || Ii.depth!=Ir.depth || Ii.dim!=Ir.dim)
        throw CImgInstanceException("CImgList<%s>::FFT() : Real (%u,%u,%u,%u,%p) and Imaginary (%u,%u,%u,%u,%p) parts "
                                    "of the instance image have different dimensions",
                                    pixel_type(),Ir.width,Ir.height,Ir.depth,Ir.dim,Ir.data,
                                    Ii.width,Ii.height,Ii.depth,Ii.dim,Ii.data);
#ifdef cimg_use_fftw3
      fftw_complex *data_in = (fftw_complex*)fftw_malloc(sizeof(fftw_complex) * Ir.width*Ir.height*Ir.depth);
      fftw_plan data_plan;
      const unsigned int w = Ir.width, wh = w*Ir.height, whd = wh*Ir.depth;
      data_plan = fftw_plan_dft_3d(Ir.width, Ir.height, Ir.depth, data_in, data_in, invert?FFTW_BACKWARD:FFTW_FORWARD, FFTW_ESTIMATE);
      cimg_forV(Ir,k) {
        T *ptrr = Ir.ptr(0,0,0,k), *ptri = Ii.ptr(0,0,0,k);
        double *ptrd = (double*)data_in;
        cimg_forX(Ir,x) {
          cimg_forY(Ir,y) {
            cimg_forZ(Ir,z) {
              *(ptrd++) = (double)*ptrr;
              *(ptrd++) = (double)*ptri;
              ptrr+=wh;
              ptri+=wh;
            }
            ptrr-=whd-w;
            ptri-=whd-w;
          }
          ptrr-=wh-1;
          ptri-=wh-1;
        }
        fftw_execute(data_plan);
        ptrd = (double*)data_in;
        ptrr = Ir.ptr(0,0,0,k);
        ptri = Ii.ptr(0,0,0,k);
        { cimg_forX(Ir,x) {
          cimg_forY(Ir,y) {
            cimg_forZ(Ir,z) {
              *ptrr = (T)*(ptrd++);
              *ptri = (T)*(ptrd++);
              ptrr+=wh;
              ptri+=wh;
            }
            ptrr-=whd-w;
            ptri-=whd-w;
          }
          ptrr-=wh-1;
          ptri-=wh-1;
        }}
      }
      fftw_destroy_plan(data_plan);
      fftw_free(data_in);
#else
      if (Ir.depth>1)  FFT('z',invert);
      if (Ir.height>1) FFT('y',invert);
      if (Ir.width>1)  FFT('x',invert);
#endif
      return *this;
    }

    //@}
    //----------------------------------
    //
    //! \name Input-Output and Display
    //@{
    //----------------------------------

    //! Print informations about the list on the standard output.
    const CImgList<T>& print(const char* title=0, const bool display_stats=true) const {
      unsigned long msiz = 0;
      cimglist_for(*this,l) msiz += data[l].size();
      msiz*=sizeof(T);
      const unsigned int mdisp = msiz<8*1024?0:(msiz<8*1024*1024?1:2);
      char ntitle[64] = { 0 };
      if (!title) std::sprintf(ntitle,"CImgList<%s>",pixel_type());
      std::fprintf(stderr,"%s: this = %p, size = %u [%lu %s], data = (CImg<%s>*)%p.\n",
                   title?title:ntitle,(void*)this,size,
                   mdisp==0?msiz:(mdisp==1?(msiz>>10):(msiz>>20)),
                   mdisp==0?"b":(mdisp==1?"Kb":"Mb"),
                   pixel_type(),(void*)data);
      char tmp[16] = { 0 };
      cimglist_for(*this,l) {
        std::sprintf(tmp,"[%d]",l);
        std::fprintf(stderr,"  ");
        data[l].print(tmp,display_stats);
        if (l==3 && size>8) { l = size-5; std::fprintf(stderr,"  ...\n"); }
      }
      return *this;
    }

    //! Load an image list from a file.
    static CImgList<T> get_load(const char *const filename) {
      return CImgList<T>().load(filename);
    }

    //! In-place version of the previous function.
    CImgList<T>& load(const char *const filename) {
      const char *ext = cimg::filename_split(filename);
#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::strncasecmp(ext,"tiff",4)) return load_tiff(filename);
      if (!cimg::strcasecmp(ext,"cimg") || !ext[0]) return load_cimg(filename);
      if (!cimg::strcasecmp(ext,"rec") ||
          !cimg::strcasecmp(ext,"par")) return load_parrec(filename);
      if (!cimg::strcasecmp(ext,"avi") ||
          !cimg::strcasecmp(ext,"mov") ||
          !cimg::strcasecmp(ext,"mpg") ||
          !cimg::strcasecmp(ext,"mpeg") ||
	  !cimg::strcasecmp(ext,"ogg") ||
	  !cimg::strcasecmp(ext,"flv")) return load_ffmpeg(filename);
      assign(1);
      data->load(filename);
      return *this;
    }

    //! Load an image list from a .cimg file.
    static CImgList<T> get_load_cimg(std::FILE *const file, const char *const filename=0) {
      return CImgList<T>().load_cimg(file,filename);
    }

    //! In-place version of the previous function.
    CImgList<T>& load_cimg(std::FILE *const file, const char *const filename=0) {
#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') tmp[j++] = (char)i; tmp[j] = '\0'; \
          W = H = D = V = 0; \
          if (std::sscanf(tmp,"%u %u %u %u",&W,&H,&D,&V)!=4) \
            throw CImgIOException("CImgList<%s>::load_cimg() : File '%s', Image %u has an invalid size (%u,%u,%u,%u)\n", \
                                  pixel_type(), filename?filename:("(FILE*)"), W, H, D, V); \
          if (W*H*D*V>0) { \
            CImg<Tss> raw; \
            CImg<T> &img = data[l]; \
            img.assign(W,H,D,V); \
            T *ptrd = img.data; \
            for (int toread = (int)img.size(); toread>0; ) { \
              raw.assign(cimg::min(toread,cimg_iobuffer)); \
              cimg::fread(raw.data,raw.width,nfile); \
              if (endian!=cimg::endian()) cimg::endian_swap(raw.data,raw.width); \
              toread-=raw.width; \
              const Tss *ptrs = raw.data; \
              for (unsigned int off = raw.width; off; --off) *(ptrd++) = (T)*(ptrs++); \
            } \
          } \
        } \
        loaded = true; \
      }
      typedef unsigned char uchar;
      typedef unsigned short ushort;
      typedef unsigned int uint;
      typedef unsigned long ulong;
      const int cimg_iobuffer = 12*1024*1024;
      std::FILE *const nfile = file?file:cimg::fopen(filename,"rb");
      bool loaded = false, endian = cimg::endian();
      char tmp[256], str_pixeltype[256], str_endian[256];
      unsigned int j, err, N=0, W, H, D, V;
      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<%s>::load_cimg() : File '%s', Unknown CImg RAW header.",
                              pixel_type(),filename?filename:"(FILE*)");
      }
      if (!cimg::strncasecmp("little",str_endian,6)) endian = false;
      else if (!cimg::strncasecmp("big",str_endian,3)) endian = true;
      assign(N);
      cimg_load_cimg_case("bool",bool);
      cimg_load_cimg_case("unsigned_char",uchar);
      cimg_load_cimg_case("uchar",uchar);
      cimg_load_cimg_case("char",char);
      cimg_load_cimg_case("unsigned_short",ushort);
      cimg_load_cimg_case("ushort",ushort);
      cimg_load_cimg_case("short",short);
      cimg_load_cimg_case("unsigned_int",uint);
      cimg_load_cimg_case("uint",uint);
      cimg_load_cimg_case("int",int);
      cimg_load_cimg_case("unsigned_long",ulong);
      cimg_load_cimg_case("ulong",ulong);
      cimg_load_cimg_case("long",long);
      cimg_load_cimg_case("float",float);
      cimg_load_cimg_case("double",double);
      if (!loaded) {
        if (!file) cimg::fclose(nfile);
        throw CImgIOException("CImgList<%s>::load_cimg() : File '%s', cannot read images of pixels coded as '%s'.",
                              pixel_type(),filename?filename:"(FILE*)",str_pixeltype);
      }
      if (!file) cimg::fclose(nfile);
      return *this;
    }

    //! Load an image list from a .cimg file.
    static CImgList<T> get_load_cimg(const char *const filename) {
      return CImgList<T>().load_cimg(0,filename);
    }

    //! In-place version of the previous function.
    CImgList<T>& load_cimg(const char *const filename) {
      return load_cimg(0,filename);
    }

    //! Load a sub-image list from a .cimg file.
    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 v0,
                                     const unsigned int x1, const unsigned int y1, const unsigned int z1, const unsigned int v1) {
      return CImgList<T>().load_cimg(file,n0,n1,x0,y0,z0,v0,x1,y1,z1,v1);
    }

    //! In-place version of the previous function.
    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 v0,
                           const unsigned int x1, const unsigned int y1, const unsigned int z1, const unsigned int v1) {
      return load_cimg(file,0,n0,n1,x0,y0,z0,v0,x1,y1,z1,v1);
    }

    //! Load a sub-image list from a .cimg file.
    static CImgList<T> get_load_cimg(const char *const filename,
                                     const unsigned int n0, const unsigned int n1,
                                     const unsigned int x0, const unsigned int y0, const unsigned int z0, const unsigned int v0,
                                     const unsigned int x1, const unsigned int y1, const unsigned int z1, const unsigned int v1) {
      return CImgList<T>().load_cimg(filename,n0,n1,x0,y0,z0,v0,x1,y1,z1,v1);
    }

    //! In-place version of the previous function.
    CImgList<T>& load_cimg(const char *const filename,
                           const unsigned int n0, const unsigned int n1,
                           const unsigned int x0, const unsigned int y0, const unsigned int z0, const unsigned int v0,
                           const unsigned int x1, const unsigned int y1, const unsigned int z1, const unsigned int v1) {
      return load_cimg(0,filename,n0,n1,x0,y0,z0,v0,x1,y1,z1,v1);
    }

    //! In-place version of the previous function.
    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 v0,
                           const unsigned int x1, const unsigned int y1, const unsigned int z1, const unsigned int v1) {
#define cimg_load_cimg_case2(Ts,Tss) \
      if (!loaded && !cimg::strcasecmp(Ts,str_pixeltype)) { \
        for (unsigned int l=0; l<=nn1; ++l) { \
          j = 0; while((i=std::fgetc(nfile))!='\n') tmp[j++]=(char)i; tmp[j]='\0'; \
          W = H = D = V = 0; \
          if (std::sscanf(tmp,"%u %u %u %u",&W,&H,&D,&V)!=4) \
            throw CImgIOException("CImgList<%s>::load_cimg() : File '%s', Image %u has an invalid size (%u,%u,%u,%u)\n", \
                                  pixel_type(), filename?filename:("(FILE*)"), W, H, D, V); \
          if (W*H*D*V>0) { \
            if (l<n0 || x0>=W || y0>=H || z0>=D || v0>=D) std::fseek(nfile,W*H*D*V*sizeof(Tss),SEEK_CUR); \
            else { \
              const unsigned int \
                nx1 = x1>=W?W-1:x1, \
                ny1 = y1>=H?H-1:y1, \
                nz1 = z1>=D?D-1:z1, \
                nv1 = v1>=V?V-1:v1; \
              CImg<Tss> raw(1+nx1-x0); \
              CImg<T> &img = data[l-n0]; \
              img.assign(1+nx1-x0,1+ny1-y0,1+nz1-z0,1+nv1-v0); \
              T *ptrd = img.data; \
              const unsigned int skipvb = v0*W*H*D*sizeof(Tss); \
              if (skipvb) std::fseek(nfile,skipvb,SEEK_CUR); \
              for (unsigned int v=1+nv1-v0; v; --v) { \
                const unsigned int skipzb = z0*W*H*sizeof(Tss); \
                if (skipzb) 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::endian()) cimg::endian_swap(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 = (V-1-nv1)*W*H*D*sizeof(Tss); \
              if (skipve) std::fseek(nfile,skipve,SEEK_CUR); \
            } \
          } \
        } \
        loaded = true; \
      }
      typedef unsigned char uchar;
      typedef unsigned short ushort;
      typedef unsigned int uint;
      typedef unsigned long ulong;
      if (n1<n0 || x1<x0 || y1<y0 || z1<z0 || v1<v0)
        throw CImgArgumentException("CImgList<%s>::load_cimg() : File '%s', Bad sub-region coordinates [%u->%u] "
                                    "(%u,%u,%u,%u)->(%u,%u,%u,%u).",pixel_type(),filename?filename:"(FILE*)",
                                    n0,n1,x0,y0,z0,v0,x1,y1,z1,v1);
      std::FILE *const nfile = file?file:cimg::fopen(filename,"rb");
      bool loaded = false, endian = cimg::endian();
      char tmp[256], str_pixeltype[256], str_endian[256];
      unsigned int j, err, N, W, H, D, V;
      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<%s>::load_cimg() : File '%s', Unknown CImg RAW header.",
                              pixel_type(),filename?filename:"(FILE*)");
      }
      if (!cimg::strncasecmp("little",str_endian,6)) endian = false;
      else if (!cimg::strncasecmp("big",str_endian,3)) endian = true;
      const unsigned int nn1 = n1>=N?N-1:n1;
      assign(1+nn1-n0);
      cimg_load_cimg_case2("bool",bool);
      cimg_load_cimg_case2("unsigned_char",uchar);
      cimg_load_cimg_case2("uchar",uchar);
      cimg_load_cimg_case2("char",char);
      cimg_load_cimg_case2("unsigned_short",ushort);
      cimg_load_cimg_case2("ushort",ushort);
      cimg_load_cimg_case2("short",short);
      cimg_load_cimg_case2("unsigned_int",uint);
      cimg_load_cimg_case2("uint",uint);
      cimg_load_cimg_case2("int",int);
      cimg_load_cimg_case2("unsigned_long",ulong);
      cimg_load_cimg_case2("ulong",ulong);
      cimg_load_cimg_case2("long",long);
      cimg_load_cimg_case2("float",float);
      cimg_load_cimg_case2("double",double);
      if (!loaded) {
        if (!file) cimg::fclose(nfile);
        throw CImgIOException("CImgList<%s>::load_cimg() : File '%s', cannot read images of pixels coded as '%s'.",
                              pixel_type(),filename?filename:"(FILE*)",str_pixeltype);
      }
      if (!file) cimg::fclose(nfile);
      return *this;
    }

    //! Load an image list from a PAR/REC (Philips) file.
    static CImgList<T> get_load_parrec(const char *const filename) {
      return CImgList<T>().load_parrec(filename);
    }

    //! In-place version of the previous function.
    CImgList<T>& load_parrec(const char *const filename) {
      char body[1024], filenamepar[1024], filenamerec[1024];
      const char *ext = cimg::filename_split(filename,body);
      if (!cimg::strncmp(ext,"par",3)) { std::strcpy(filenamepar,filename); std::sprintf(filenamerec,"%s.rec",body); }
      if (!cimg::strncmp(ext,"PAR",3)) { std::strcpy(filenamepar,filename); std::sprintf(filenamerec,"%s.REC",body); }
      if (!cimg::strncmp(ext,"rec",3)) { std::strcpy(filenamerec,filename); std::sprintf(filenamepar,"%s.par",body); }
      if (!cimg::strncmp(ext,"REC",3)) { std::strcpy(filenamerec,filename); std::sprintf(filenamepar,"%s.PAR",body); }
      std::FILE *file = cimg::fopen(filenamepar,"r");

      // Parse header file
      CImgList<float> st_slices;
      CImgList<unsigned int> st_global;
      int err;
      char line[256] = { 0 };
      do { err=std::fscanf(file,"%255[^\n]%*c",line); } while (err!=EOF && (line[0]=='#' || line[0]=='.'));
      do {
        unsigned int sn,sizex,sizey,pixsize;
        float rs,ri,ss;
        err=std::fscanf(file,"%u%*u%*u%*u%*u%*u%*u%u%*u%u%u%g%g%g%*[^\n]",&sn,&pixsize,&sizex,&sizey,&ri,&rs,&ss);
        if (err==7) {
          st_slices.insert(CImg<float>::vector((float)sn,(float)pixsize,(float)sizex,(float)sizey,ri,rs,ss,0));
          unsigned int i; for (i=0; i<st_global.size && sn<=st_global[i][2]; ++i) {}
          if (i==st_global.size) st_global.insert(CImg<unsigned int>::vector(sizex,sizey,sn));
          else {
            CImg<unsigned int> &vec = st_global[i];
            if (sizex>vec[0]) vec[0] = sizex;
            if (sizey>vec[1]) vec[1] = sizey;
            vec[2] = sn;
          }
          st_slices[st_slices.size-1][7] = (float)i;
        }
      } while (err==7);

      // Read data
      std::FILE *file2 = cimg::fopen(filenamerec,"rb");
      { cimglist_for(st_global,l) {
        const CImg<unsigned int>& vec = st_global[l];
        insert(CImg<T>(vec[0],vec[1],vec[2]));
      }}

      cimglist_for(st_slices,l) {
        const CImg<float>& 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<unsigned char> buf(sizex,sizey);
          cimg::fread(buf.data,sizex*sizey,file2);
          if (cimg::endian()) cimg::endian_swap(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<unsigned short> buf(sizex,sizey);
          cimg::fread(buf.data,sizex*sizey,file2);
          if (cimg::endian()) cimg::endian_swap(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<unsigned int> buf(sizex,sizey);
          cimg::fread(buf.data,sizex*sizey,file2);
          if (cimg::endian()) cimg::endian_swap(buf.data,sizex*sizey);
          CImg<T>& img = (*this)[imn];
          cimg_forXY(img,x,y) img(x,y,sn) = (T)(( buf(x,y)*rs + ri )/(rs*ss));
        } break;
        default:
          cimg::fclose(file);
          cimg::fclose(file2);
          throw CImgIOException("CImg<%s>::load_parrec() : File '%s', cannot handle image with pixsize = %d bits.",
                                pixel_type(),filename,pixsize);
          break;
        }
      }
      cimg::fclose(file);
      cimg::fclose(file2);
      if (!size)
        throw CImgIOException("CImg<%s>::load_parrec() : File '%s' does not appear to be a valid PAR-REC file.",
                              pixel_type(),filename);
      return *this;
    }

    //! Load an image sequence from a YUV file.
    static CImgList<T> get_load_yuv(std::FILE *const file, const char *const filename,
                                    const unsigned int sizex, const unsigned int sizey=1,
                                    const unsigned int first_frame=0, const unsigned int last_frame=~0U,
                                    const unsigned int step_frame=1, const bool yuv2rgb=true) {
      return CImgList<T>().load_yuv(file,filename,sizex,sizey,first_frame,last_frame,step_frame,yuv2rgb);
    }

    //! In-place version of the previous function.
    CImgList<T>& load_yuv(std::FILE *const file, const char *const filename,
                          const unsigned int sizex, const unsigned int sizey,
                          const unsigned int first_frame=0, const unsigned int last_frame=~0U,
                          const unsigned int step_frame=1, const bool yuv2rgb=true) {
      if (sizex%2 || sizey%2)
        throw CImgArgumentException("CImgList<%s>::load_yuv() : File '%s', image dimensions along X and Y must be "
                                    "even numbers (given are %ux%u)\n",pixel_type(),filename?filename:"(unknown)",sizex,sizey);
      if (!sizex || !sizey)
        throw CImgArgumentException("CImgList<%s>::load_yuv() : File '%s', given image sequence size (%u,%u) is invalid",
                                    pixel_type(),filename?filename:"(unknown)",sizex,sizey);

      const unsigned int
	nfirst_frame = first_frame<last_frame?first_frame:last_frame,
	nlast_frame = first_frame<last_frame?last_frame:first_frame,
	nstep_frame = step_frame?step_frame:1;

      CImg<unsigned char> tmp(sizex,sizey,1,3), UV(sizex/2,sizey/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*(sizex*sizey + sizex*sizey/2),SEEK_CUR);
        if (err) {
          if (!file) cimg::fclose(nfile);
          throw CImgIOException("CImgList<%s>::load_yuv() : File '%s' doesn't contain frame number %u "
                                "(out of range error).",pixel_type(),filename?filename:"(FILE*)",nfirst_frame);
        }
      }
      unsigned int frame;
      for (frame = nfirst_frame; !stopflag && frame<=nlast_frame; frame+=nstep_frame) {
        tmp.fill(0);
        // *TRY* to read the luminance part, do not replace by cimg::fread !
        err = (int)std::fread((void*)(tmp.data),1,(size_t)(tmp.width*tmp.height),nfile);
        if (err!=(int)(tmp.width*tmp.height)) {
          stopflag = true;
          if (err>0) cimg::warn("CImgList<%s>::load_yuv() : File '%s' contains incomplete data,"
                                " or given image dimensions (%u,%u) are incorrect.",
                                pixel_type(),filename?filename:"(unknown)",sizex,sizey);
        } 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<%s>::load_yuv() : File '%s' contains incomplete data,"
                                  " or given image dimensions (%u,%u) are incorrect.",
                                  pixel_type(),filename?filename:"(unknown)",sizex,sizey);
          } else {
            cimg_forXY(UV,x,y) {
              const int x2 = x*2, y2 = y*2;
              tmp(x2,y2,1) = tmp(x2+1,y2,1) = tmp(x2,y2+1,1) = tmp(x2+1,y2+1,1) = UV(x,y,0);
              tmp(x2,y2,2) = tmp(x2+1,y2,2) = tmp(x2,y2+1,2) = tmp(x2+1,y2+1,2) = UV(x,y,1);
            }
            if (yuv2rgb) tmp.YCbCrtoRGB();
            insert(tmp);
	    if (nstep_frame>1) std::fseek(nfile,(nstep_frame-1)*(sizex*sizey + sizex*sizey/2),SEEK_CUR);
          }
        }
      }
      if (stopflag && nlast_frame!=~0U && frame!=nlast_frame)
        cimg::warn("CImgList<%s>::load_yuv() : File '%s', frame %d not reached since only %u frames were found in the file.",
                   pixel_type(),filename?filename:"(unknown)",nlast_frame,frame-1,filename);
      if (!file) cimg::fclose(nfile);
      return *this;
    }

    //! Load an image sequence from a YUV file.
    static CImgList<T> get_load_yuv(const char *const filename,
                                    const unsigned int sizex, const unsigned int sizey=1,
                                    const unsigned int first_frame=0, const unsigned int last_frame=~0U,
                                    const unsigned int step_frame=1, const bool yuv2rgb=true) {
      return CImgList<T>().load_yuv(0,filename,sizex,sizey,first_frame,last_frame,step_frame,yuv2rgb);
    }

    //! In-place version of the previous function.
    CImgList<T>& load_yuv(const char *const filename,
                          const unsigned int sizex, const unsigned int sizey,
                          const unsigned int first_frame=0, const unsigned int last_frame=~0U,
                          const unsigned int step_frame=1, const bool yuv2rgb=true) {
      return load_yuv(0,filename,sizex,sizey,first_frame,last_frame,step_frame,yuv2rgb);
    }

    //! Load an image from a video file, using ffmpeg libraries
    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);
    }

    //! In-place version of the previous function.
    // 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) {
      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) && (pixel_format || !pixel_format)) || resume)
        throw CImgArgumentException("CImg<%s>::load_tiff() : File '%s', reading sub-frames from a video file requires the use of ffmpeg.\n"
                                    "('cimg_use_ffmpeg' must be defined).",pixel_type(),filename);
      return load_ffmpeg_external(filename);
#else
      const unsigned int ffmpeg_pixfmt = pixel_format?PIX_FMT_RGB24:PIX_FMT_GRAY8;
      static bool first_time = true;
      static AVFormatContext *format_ctx = 0;
      static AVCodecContext *codec_ctx = 0;
      static AVCodec *codec = 0;
      static AVFrame *avframe = 0, *converted_frame = 0;
      static unsigned int vstream = 0;
      if (first_time) { av_register_all(); first_time = false; }

      if (resume) {
        if (!format_ctx || !codec_ctx || !codec || !avframe || !converted_frame)
          throw CImgArgumentException("CImgList<%s>::load_ffmpeg() : File '%s', cannot resume due to unallocated FFMPEG structures.",
                                      pixel_type(),filename);
      } else {
        // Allocate ffmpeg structures.
        if (!format_ctx) format_ctx = new AVFormatContext; else av_close_input_file(format_ctx);
        if (!codec_ctx) codec_ctx = new AVCodecContext;
        if (!codec) codec = new AVCodec;
        if (!avframe) avframe = avcodec_alloc_frame();
        if (!converted_frame) converted_frame = avcodec_alloc_frame();
        if (!format_ctx || !codec_ctx || !codec || !avframe || !converted_frame) {
          cimg::warn("CImgList<%s>::load_ffmpeg() : File '%s', cannot allocate FFMPEG structures.\n"
                     "Trying with external ffmpeg executable.",pixel_type(),filename);
          if (format_ctx) { delete format_ctx; format_ctx = 0; }
          if (codec_ctx) { delete codec_ctx; codec_ctx = 0; }
          if (codec) { delete codec; codec = 0; }
          if (avframe) { delete avframe; avframe = 0; }
          if (converted_frame) { delete converted_frame; converted_frame = 0; }
          return load_ffmpeg_external(filename);
        }

        // Open video file, find video stream and codec.
        if (av_open_input_file(&format_ctx,filename,0,0,0)!=0)
          throw CImgIOException("CImgList<%s>::load_ffmpeg() : File '%s' cannot be opened.",pixel_type(),filename);
        if (av_find_stream_info(format_ctx)<0) {
          cimg::warn("CImgList<%s>::load_ffmpeg() : File '%s', cannot retrieve stream information.\n"
                     "Trying with external ffmpeg executable.",pixel_type(),filename);
          return load_ffmpeg_external(filename);
        }
#if cimg_debug>=3
        dump_format(format_ctx,0,0,0);
#endif
        for (vstream = 0; vstream<format_ctx->nb_streams && format_ctx->streams[vstream]->codec->codec_type!=CODEC_TYPE_VIDEO; ) ++vstream;
        if (vstream==format_ctx->nb_streams) {
          cimg::warn("CImgList<%s>::load_ffmpeg() : File '%s', cannot retrieve video stream.\n"
                     "Trying with external ffmpeg executable.",pixel_type(),filename);
          av_close_input_file(format_ctx);
          return load_ffmpeg_external(filename);
        }
        codec_ctx = format_ctx->streams[vstream]->codec;
        codec = avcodec_find_decoder(codec_ctx->codec_id);
        if (!codec) {
          cimg::warn("CImgList<%s>::load_ffmpeg() : File '%s', cannot find video codec.\n"
                     "Trying with external ffmpeg executable.",pixel_type(),filename);
          return load_ffmpeg_external(filename);
        }
        if (avcodec_open(codec_ctx,codec)<0) { // Open codec
          cimg::warn("CImgList<%s>::load_ffmpeg() : File '%s', cannot open video codec.\n"
                     "Trying with external ffmpeg executable.",pixel_type(),filename);
          return load_ffmpeg_external(filename);
        }
      }

      // Read video frames
      const unsigned int numBytes = avpicture_get_size(ffmpeg_pixfmt,codec_ctx->width,codec_ctx->height);
      uint8_t *const buffer = new uint8_t[numBytes];
      avpicture_fill((AVPicture *)converted_frame,buffer,ffmpeg_pixfmt,codec_ctx->width,codec_ctx->height);

      const T foo = (T)0;
      AVPacket packet;
      for (unsigned int frame = 0, next_frame = nfirst_frame; frame<=nlast_frame && av_read_frame(format_ctx,&packet)>=0; ) {
	if (packet.stream_index==(int)vstream) {
          int frame_finished = 0;
	  avcodec_decode_video(codec_ctx,avframe,&frame_finished,packet.data,packet.size);
	  if (frame_finished) {
	    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<unsigned char> next_image(*converted_frame->data,3,codec_ctx->width,codec_ctx->height,1,true);
		insert(next_image.get_permute_axes("yzvx",foo));
	      } else {
		CImg<unsigned char> next_image(*converted_frame->data,1,codec_ctx->width,codec_ctx->height,1,true);
		insert(next_image.get_permute_axes("yzvx",foo));
	      }
	      next_frame+=nstep_frame;
	    }
	    ++frame;
	  }
	  av_free_packet(&packet);
	  if (next_frame>nlast_frame) break;
	}
      }
      delete[] buffer;
      return *this;
#endif
    }

    //! Load an image from a video file (MPEG,AVI) using the external tool 'ffmpeg'
    static CImgList<T> get_load_ffmpeg_external(const char *const filename) {
      return CImgList<T>().load_ffmpeg_external(filename);
    }

    //! In-place version of the previous function.
    CImgList<T>& load_ffmpeg_external(const char *const filename) {
      char command[1024], filetmp[512], filetmp2[512];
      std::FILE *file = 0;
      do {
        std::sprintf(filetmp,"%s%s%s",cimg::temporary_path(),cimg_OS==2?"\\":"/",cimg::filenamerand());
        std::sprintf(filetmp2,"%s_000001.ppm",filetmp);
        if ((file=std::fopen(filetmp2,"rb"))!=0) std::fclose(file);
      } while (file);
      std::sprintf(filetmp2,"%s_%%6d.ppm",filetmp);
#if cimg_OS!=2
      std::sprintf(command,"ffmpeg -i \"%s\" %s >/dev/null 2>&1",filename,filetmp2);
#else
      std::sprintf(command,"ffmpeg -i \"%s\" %s",filename,filetmp2);
#endif
      cimg::system(command,0);
      const unsigned int odebug = cimg::exception_mode();
      cimg::exception_mode() = 0;
      assign();
      unsigned int i = 1;
      for (bool stopflag = false; !stopflag; ++i) {
        std::sprintf(filetmp2,"%s_%.6u.ppm",filetmp,i);
        CImg<T> img;
        try { img.load_pnm(filetmp2); }
        catch (CImgException&) { stopflag = true; }
        if (img) { insert(img); std::remove(filetmp2); }
      }
      cimg::exception_mode() = odebug;
      if (is_empty())
        throw CImgIOException("CImgList<%s>::load_ffmpeg_external() : Failed to open image sequence '%s'.\n"
                              "Check the filename and if the 'ffmpeg' tool is installed on your system.",
                              pixel_type(),filename);
      return *this;
    }

    //! Load a 3D object from a .OFF file (GeomView 3D object files).
    template<typename tf, typename tc>
      static CImgList<T> get_load_off(std::FILE *const file, const char *const filename,
                                      CImgList<tf>& primitives, CImgList<tc>& colors,
                                      const bool invert_faces=false) {
      return CImgList<T>().load_off(file,filename,primitives,colors,invert_faces);
    }

    //! In-place version of the previous function.
    template<typename tf, typename tc>
      CImgList<T>& load_off(std::FILE *const file, const char *const filename,
                            CImgList<tf>& primitives, CImgList<tc>& colors,
                            const bool invert_faces=false) {
      return assign(CImg<T>::get_load_off(file,filename,primitives,colors,invert_faces).get_split('x'));
    }

    //! Load a 3D object from a .OFF file (GeomView 3D object files).
    template<typename tf, typename tc>
      static CImgList<T> get_load_off(const char *const filename,
                                      CImgList<tf>& primitives, CImgList<tc>& colors,
                                      const bool invert_faces=false) {
      return CImgList<T>().load_off(0,filename,primitives,colors,invert_faces);
    }

    //! In-place version of the previous function.
    template<typename tf, typename tc>
    CImgList<T>& load_off(const char *const filename, CImgList<tf>& primitives, CImgList<tc>& colors,
                          const bool invert_faces=false) {
      return load_off(0,filename,primitives,colors,invert_faces);
    }

    //! Load a (possibly multi-page) tiff file
    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);
    }

    //! In-place version of the previous function.
    CImgList<T>& load_tiff(const char *const filename,
			   const unsigned int first_frame=0, const unsigned int last_frame=~0U,
			   const unsigned int step_frame=1) {
      const unsigned int
	nfirst_frame = first_frame<last_frame?first_frame:last_frame,
	nstep_frame = step_frame?step_frame:1;
      unsigned int nlast_frame = first_frame<last_frame?last_frame:first_frame;
#ifndef cimg_use_tiff
      if (nfirst_frame || nlast_frame!=~0U || nstep_frame!=1)
        throw CImgArgumentException("CImgList<%s>::load_tiff() : File '%s', reading sub-images from a tiff file requires the use of libtiff.\n"
                                    "('cimg_use_tiff' must be defined).",pixel_type(),filename);
      return assign(CImg<T>::get_load_tiff(filename));
#else
      TIFF *tif = TIFFOpen(filename,"r");
      if (tif) {
        unsigned int nb_images = 0;
        do ++nb_images; while (TIFFReadDirectory(tif));
        if (nfirst_frame>=nb_images || (nlast_frame!=~0U && nlast_frame>=nb_images))
          cimg::warn("CImgList<%s>::load_tiff() : File '%s' contains %u image(s), specified frame range is [%u,%u] (step %u).",
                     pixel_type(),filename,nb_images,nfirst_frame,nlast_frame,nstep_frame);
        if (nfirst_frame>=nb_images) return assign();
        if (nlast_frame>=nb_images) nlast_frame = nb_images-1;
        assign(1+(nlast_frame-nfirst_frame)/nstep_frame);
        TIFFSetDirectory(tif,0);
#if cimg_debug>=3
        TIFFSetWarningHandler(0);
        TIFFSetErrorHandler(0);
#endif
        cimglist_for(*this,l) data[l]._load_tiff(tif,nfirst_frame+l*nstep_frame);
        TIFFClose(tif);
      } else throw CImgException("CImgList<%s>::load_tiff() : File '%s', cannot open file.",pixel_type(),filename);
      return *this;
#endif
    }

    //! Save an image list into a file.
    /**
       Depending on the extension of the given filename, a file format is chosen for the output file.
    **/
    const CImgList<T>& save(const char *const filename, const int number=-1) const {
      if (is_empty()) throw CImgInstanceException("CImgList<%s>::save() : Instance list (%u,%p) is empty (file '%s').",
                                                  pixel_type(),size,data,filename);
      if (!filename) throw CImgArgumentException("CImg<%s>::save() : Instance list (%u,%p), specified filename is (null).",
                                                 pixel_type(),size,data);
      const char *ext = cimg::filename_split(filename);
      char nfilename[1024];
      const char *const fn = (number>=0)?cimg::filename_number(filename,number,6,nfilename):filename;
#ifdef cimglist_save_plugin
      cimglist_save_plugin(fn);
#endif
#ifdef cimglist_save_plugin1
      cimglist_save_plugin1(fn);
#endif
#ifdef cimglist_save_plugin2
      cimglist_save_plugin2(fn);
#endif
#ifdef cimglist_save_plugin3
      cimglist_save_plugin3(fn);
#endif
#ifdef cimglist_save_plugin4
      cimglist_save_plugin4(fn);
#endif
#ifdef cimglist_save_plugin5
      cimglist_save_plugin5(fn);
#endif
#ifdef cimglist_save_plugin6
      cimglist_save_plugin6(fn);
#endif
#ifdef cimglist_save_plugin7
      cimglist_save_plugin7(fn);
#endif
#ifdef cimglist_save_plugin8
      cimglist_save_plugin8(fn);
#endif
#ifdef cimg_use_tiff
      if (!cimg::strncasecmp(ext,"tif",3) || !cimg::strncasecmp(ext,"tiff",4)) return save_tiff(fn);
#endif
      if (!cimg::strncasecmp(ext,"cimg",4) || !ext[0]) return save_cimg(fn);
      if (!cimg::strncasecmp(ext,"yuv",3)) return save_yuv(fn,true);
      if (!cimg::strncasecmp(ext,"avi",3) ||
          !cimg::strncasecmp(ext,"mov",3) ||
          !cimg::strncasecmp(ext,"mpg",3) ||
          !cimg::strncasecmp(ext,"mpeg",4)) return save_ffmpeg_external(fn);
      if (size==1) data[0].save(fn,-1);
      else cimglist_for(*this,l) data[l].save(fn,l);
      return *this;
    }

    //! Save an image sequence into a YUV file
    const CImgList<T>& save_yuv(std::FILE *const file, const char *const filename=0, const bool rgb2yuv=true) const {
      if (is_empty()) throw CImgInstanceException("CImgList<%s>::save_yuv() : Instance list (%u,%p) is empty (file '%s').",
                                                  pixel_type(),size,data,filename?filename:"(unknown)");
      if (!file && !filename) throw CImgArgumentException("CImg<%s>::save_yuv() : Instance list (%u,%p), specified file is (null).",
                                                          pixel_type(),size,data);
      if ((*this)[0].dimx()%2 || (*this)[0].dimy()%2)
        throw CImgInstanceException("CImgList<%s>::save_yuv() : Image dimensions must be even numbers (current are %ux%u, file '%s').",
                                    pixel_type(),(*this)[0].dimx(),(*this)[0].dimy(),filename?filename:"(unknown)");

      std::FILE *const nfile = file?file:cimg::fopen(filename,"wb");
      cimglist_for(*this,l) {
        CImg<unsigned char> YCbCr((*this)[l]);
        if (rgb2yuv) YCbCr.RGBtoYCbCr();
        cimg::fwrite(YCbCr.data,YCbCr.width*YCbCr.height,nfile);
        cimg::fwrite(YCbCr.get_resize(YCbCr.width/2, YCbCr.height/2,1,3,3).ptr(0,0,0,1),
                     YCbCr.width*YCbCr.height/2,nfile);
      }
      if (!file) cimg::fclose(nfile);
      return *this;
    }

    //! Save an image sequence into a YUV file
    const CImgList<T>& save_yuv(const char *const filename=0, const bool rgb2yuv=true) const {
      return save_yuv(0,filename,rgb2yuv);
    }

    //! Save an image list into a .cimg file.
    /**
       A CImg RAW file is a simple uncompressed binary file that may be used to save list of CImg<T> images.
       \param filename : name of the output file.
       \return A reference to the current CImgList instance is returned.
    **/
    const CImgList<T>& save_cimg(std::FILE *const file, const char *const filename=0) const {
      if (is_empty()) throw CImgInstanceException("CImgList<%s>::save_cimg() : Instance list (%u,%p) is empty (file '%s').",
                                                  pixel_type(),size,data,filename?filename:"(unknown)");
      if (!file && !filename) throw CImgArgumentException("CImg<%s>::save_cimg() : Instance list (%u,%p), specified file is (null).",
                                                          pixel_type(),size,data);
      std::FILE *const nfile = file?file:cimg::fopen(filename,"wb");
      const char *const ptype = pixel_type(), *const etype = cimg::endian()?"big":"little";
      if (!cimg::strncmp(ptype,"unsigned",8)) std::fprintf(nfile,"%u unsigned_%s %s_endian\n",size,ptype+9,etype);
      else std::fprintf(nfile,"%u %s %s_endian\n",size,ptype,etype);
      cimglist_for(*this,l) {
        const CImg<T>& img = data[l];
        std::fprintf(nfile,"%u %u %u %u\n",img.width,img.height,img.depth,img.dim);
        if (img.data) {
          if (cimg::endian()) {
            CImg<T> tmp(img);
            cimg::endian_swap(tmp.data,tmp.size());
            cimg::fwrite(tmp.data,img.width*img.height*img.depth*img.dim,nfile);
          } else cimg::fwrite(img.data,img.width*img.height*img.depth*img.dim,nfile);
        }
      }
      if (!file) cimg::fclose(nfile);
      return *this;
    }

    //! Save an image list into a CImg file (RAW binary file + simple header)
    const CImgList<T>& save_cimg(const char *const filename) const {
      return save_cimg(0,filename);
    }

    // Insert the instance image into into an existing .cimg file, at specified coordinates.
    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 v0) const {
#define cimg_save_cimg_case(Ts,Tss) \
      if (!saved && !cimg::strcasecmp(Ts,str_pixeltype)) { \
        for (unsigned int l=0; l<lmax; ++l) { \
          j = 0; while((i=std::fgetc(nfile))!='\n') tmp[j++]=(char)i; tmp[j]='\0'; \
          W = H = D = V = 0; \
          if (std::sscanf(tmp,"%u %u %u %u",&W,&H,&D,&V)!=4) \
            throw CImgIOException("CImgList<%s>::save_cimg() : File '%s', Image %u has an invalid size (%u,%u,%u,%u)\n", \
                                  pixel_type(), filename?filename:("(FILE*)"), W, H, D, V); \
          if (W*H*D*V>0) { \
            if (l<n0 || x0>=W || y0>=H || z0>=D || v0>=D) std::fseek(nfile,W*H*D*V*sizeof(Tss),SEEK_CUR); \
            else { \
              const CImg<T>& img = (*this)[l-n0]; \
              const T *ptrs = img.data; \
              const unsigned int \
                x1 = x0 + img.width - 1, \
                y1 = y0 + img.height - 1, \
                z1 = z0 + img.depth - 1, \
                v1 = v0 + img.dim - 1, \
                nx1 = x1>=W?W-1:x1, \
                ny1 = y1>=H?H-1:y1, \
                nz1 = z1>=D?D-1:z1, \
                nv1 = v1>=V?V-1:v1; \
              CImg<Tss> raw(1+nx1-x0); \
              const unsigned int skipvb = v0*W*H*D*sizeof(Tss); \
              if (skipvb) std::fseek(nfile,skipvb,SEEK_CUR); \
              for (unsigned int v=1+nv1-v0; v; --v) { \
                const unsigned int skipzb = z0*W*H*sizeof(Tss); \
                if (skipzb) 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::endian_swap(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 = (V-1-nv1)*W*H*D*sizeof(Tss); \
              if (skipve) std::fseek(nfile,skipve,SEEK_CUR); \
            } \
          } \
        } \
        saved = true; \
      }
      if (is_empty()) throw CImgInstanceException("CImgList<%s>::save_cimg() : Instance list (%u,%p) is empty (file '%s').",
                                                  pixel_type(),size,data,filename?filename:"(unknown)");
      if (!file && !filename) throw CImgArgumentException("CImg<%s>::save_cimg() : Instance list (%u,%p), specified file is (null).",
                                                          pixel_type(),size,data);
      typedef unsigned char uchar;
      typedef unsigned short ushort;
      typedef unsigned int uint;
      typedef unsigned long ulong;
      std::FILE *const nfile = file?file:cimg::fopen(filename,"rb+");
      bool saved = false, endian = cimg::endian();
      char tmp[256], str_pixeltype[256], str_endian[256];
      unsigned int j, err, N, W, H, D, V;
      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<%s>::save_cimg() : File '%s', Unknown CImg RAW header.",
                              pixel_type(),filename?filename:"(FILE*)");
      }
      if (!cimg::strncasecmp("little",str_endian,6)) endian = false;
      else if (!cimg::strncasecmp("big",str_endian,3)) endian = true;
      const unsigned int lmax = cimg::min(N,n0+size);
      cimg_save_cimg_case("bool",bool);
      cimg_save_cimg_case("unsigned_char",uchar);
      cimg_save_cimg_case("uchar",uchar);
      cimg_save_cimg_case("char",char);
      cimg_save_cimg_case("unsigned_short",ushort);
      cimg_save_cimg_case("ushort",ushort);
      cimg_save_cimg_case("short",short);
      cimg_save_cimg_case("unsigned_int",uint);
      cimg_save_cimg_case("uint",uint);
      cimg_save_cimg_case("int",int);
      cimg_save_cimg_case("unsigned_long",ulong);
      cimg_save_cimg_case("ulong",ulong);
      cimg_save_cimg_case("long",long);
      cimg_save_cimg_case("float",float);
      cimg_save_cimg_case("double",double);
      if (!saved) {
        if (!file) cimg::fclose(nfile);
        throw CImgIOException("CImgList<%s>::save_cimg() : File '%s', cannot save images of pixels coded as '%s'.",
                              pixel_type(),filename?filename:"(FILE*)",str_pixeltype);
      }
      if (!file) cimg::fclose(nfile);
      return *this;
    }

    //! Insert the instance image into into an existing .cimg file, at specified coordinates.
    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 v0) const {
      return save_cimg(file,0,n0,x0,y0,z0,v0);
    }

    //! Insert the instance image into into an existing .cimg file, at specified coordinates.
    const CImgList<T>& save_cimg(const char *const filename,
                                 const unsigned int n0,
                                 const unsigned int x0, const unsigned int y0,
                                 const unsigned int z0, const unsigned int v0) const {
      return save_cimg(0,filename,n0,x0,y0,z0,v0);
    }

    // Create an empty .cimg file with specified dimensions (inner routine)
    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=1,
                                const unsigned int dz=1, const unsigned int dv=1) {
      std::FILE *const nfile = file?file:cimg::fopen(filename,"wb");
      const unsigned int siz = dx*dy*dz*dv*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,dv);
	for (unsigned int off=siz; off; --off) std::fputc(0,nfile);
      }
      if (!file) cimg::fclose(nfile);
    }

    //! Create an empty .cimg file with specified dimensions.
    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 dv=1) {
      return save_empty_cimg(file,0,nb,dx,dy,dz,dv);
    }

    //! Create an empty .cimg file with specified dimensions.
    static void save_empty_cimg(const char *const filename,
                                const unsigned int nb,
                                const unsigned int dx, const unsigned int dy=1,
                                const unsigned int dz=1, const unsigned int dv=1) {
      return save_empty_cimg(0,filename,nb,dx,dy,dz,dv);
    }

#ifdef cimg_use_tiff
    //! Save a file in TIFF format.
    const CImgList<T>& save_tiff(const char *const filename) const {
      if (is_empty()) throw CImgInstanceException("CImgList<%s>::save_tiff() : File '%s', instance list (%u,%p) is empty.",
                                                  pixel_type(),filename,size,data);
      if (!filename) throw CImgArgumentException("CImgList<%s>::save_tiff() : Specified filename is (null) for instance list (%u,%p).",
						 pixel_type(),size,data);
      TIFF *tif = TIFFOpen(filename,"w");
      if (tif) {
        for (unsigned int dir=0, l=0; l<size; ++l) {
          const CImg<T>& img = (*this)[l];
          if (img) {
            if (img.depth==1) img._save_tiff(tif,dir++);
            else cimg_forZ(img,z) img.get_slice(z)._save_tiff(tif,dir++);
          }
        }
        TIFFClose(tif);
      } else throw CImgException("CImgList<%s>::save_tiff() : File '%s', error while opening stream for tiff file.",
                                 pixel_type(),filename);
      return *this;
    }
#endif

    //! Save an image list into a OFF file.
    template<typename tf, typename tc>
    const CImgList<T>& save_off(std::FILE *const file, const char *const filename,
                                const CImgList<tf>& primitives, const CImgList<tc>& colors, const bool invert_faces=false) const {
      get_append('x','y').save_off(file,filename,primitives,colors,invert_faces);
      return *this;
    }

    //! Save an image list into a OFF file.
    template<typename tf, typename tc>
    const CImgList<T>& save_off(const char *const filename,
                                const CImgList<tf>& primitives, const CImgList<tc>& colors, const bool invert_faces=false) const {
      return save_off(0,filename,primitives,colors,invert_faces);
    }

    //! Save an image sequence using the external tool 'ffmpeg'.
    const CImgList<T>& save_ffmpeg_external(const char *const filename, const char *const codec="mpeg2video") const {
      char command[1024], filetmp[512], filetmp2[512];
      std::FILE *file = 0;

      cimglist_for(*this,ll) if (!data[ll].is_sameXYZ(data[0]))
        throw CImgInstanceException("CImgList<%s>::save_ffmpeg_external() : All images of the sequence must be of the same dimension (file '%s').",
                                    pixel_type(),filename);

      do {
        std::sprintf(filetmp,"%s%s%s",cimg::temporary_path(),cimg_OS==2?"\\":"/",cimg::filenamerand());
        std::sprintf(filetmp2,"%s_000001.ppm",filetmp);
        if ((file=std::fopen(filetmp2,"rb"))!=0) std::fclose(file);
      } while (file);
      cimglist_for(*this,l) {
        std::sprintf(filetmp2,"%s_%.6u.ppm",filetmp,l+1);
        if (data[l].depth>1 || data[l].dim!=3) data[l].get_resize(-100,-100,1,3).save_pnm(filetmp2);
        else data[l].save_pnm(filetmp2);
      }
#if cimg_OS!=2
      std::sprintf(command,"ffmpeg -i %s_%%6d.ppm -vcodec %s -sameq -y \"%s\" >/dev/null 2>&1",filetmp,codec,filename);
#else
      std::sprintf(command,"ffmpeg -i %s_%%6d.ppm -vcodec %s -sameq -y \"%s\"",filetmp,codec,filename);
#endif
      cimg::system(command);
      file = std::fopen(filename,"rb");
      if (!file) throw CImgIOException("CImg<%s>::save_ffmpeg_external() : Failed to save image sequence '%s'.\n\n",
                                       pixel_type(),filename);
      else cimg::fclose(file);
      cimglist_for(*this,lll) { std::sprintf(filetmp2,"%s_%.6u.ppm",filetmp,lll+1); std::remove(filetmp2); }
      return *this;
    }

    // Return a list where each image has been split along the specified axis
    CImgList<T> get_split(const char axe='x') const {
      CImgList<T> res;
      cimglist_for(*this,l) {
	const CImg<T> tmp = data[l].get_split(axe);
	tmp.transfer_to(res,res.size);
      }
      return res;
    }

    //! In-place version of the previous function.
    CImgList<T>& split(const char axe='x') {
      return get_split(axe).transfer_to(*this);
    }

    //! Return a single image which is the concatenation of all images of the current CImgList instance.
    /**
       \param axe : specify the axe for image concatenation. Can be 'x','y','z' or 'v'.
       \param align : specify the alignment for image concatenation. Can be 'p' (top), 'c' (center) or 'n' (bottom).
       \return A CImg<T> image corresponding to the concatenation is returned.
    **/
    CImg<T> get_append(const char axe='x', const char align='c') const {
      if (is_empty()) return CImg<T>();
      if (size==1) return +((*this)[0]);
      unsigned int dx = 0, dy = 0, dz = 0, dv = 0, pos = 0;
      CImg<T> res;
      switch(cimg::uncase(axe)) {
      case 'x': {
        switch (cimg::uncase(align)) {
        case 'x': { dy = dz = dv = 1; cimglist_for(*this,l) dx+=(*this)[l].size(); } break;
        case 'y': { dx = size; dz = dv = 1; cimglist_for(*this,l) dy = cimg::max(dy,(unsigned int)(*this)[l].size()); } break;
        case 'z': { dx = size; dy = dv = 1; cimglist_for(*this,l) dz = cimg::max(dz,(unsigned int)(*this)[l].size()); } break;
        case 'v': { dx = size; dy = dz = 1; cimglist_for(*this,l) dv = cimg::max(dz,(unsigned int)(*this)[l].size()); } break;
        default:
          cimglist_for(*this,l) {
            const CImg<T>& img = (*this)[l];
            dx += img.width;
            dy = cimg::max(dy,img.height);
            dz = cimg::max(dz,img.depth);
            dv = cimg::max(dv,img.dim);
          }
        }
        res.assign(dx,dy,dz,dv,0);
        switch (cimg::uncase(align)) {
        case 'x': {
          cimglist_for(*this,l) { res.draw_image(CImg<T>((*this)[l],true).unroll('x'),pos,0,0,0); pos+=(*this)[l].size(); }
        } break;
        case 'y': {
          cimglist_for(*this,l) res.draw_image(CImg<T>((*this)[l],true).unroll('y'),pos++,0,0,0);
        } break;
        case 'z': {
          cimglist_for(*this,l) res.draw_image(CImg<T>((*this)[l],true).unroll('z'),pos++,0,0,0);
        } break;
        case 'v': {
          cimglist_for(*this,l) res.draw_image(CImg<T>((*this)[l],true).unroll('v'),pos++,0,0,0);
        } break;
        case 'p': {
          cimglist_for(*this,l) { res.draw_image((*this)[l],pos,0,0,0); pos+=(*this)[l].width; }
        } break;
        case 'n': {
          cimglist_for(*this,l) { res.draw_image((*this)[l],pos,dy-(*this)[l].height,dz-(*this)[l].depth,dv-(*this)[l].dim); pos+=(*this)[l].width; }
        } break;
        default : {
          cimglist_for(*this,l) { res.draw_image((*this)[l],pos,(dy-(*this)[l].height)/2,(dz-(*this)[l].depth)/2,(dv-(*this)[l].dim)/2); pos+=(*this)[l].width; }
        } break;
        }
      } break;

      case 'y': {
        switch (cimg::uncase(align)) {
        case 'x': { dy = size; dz = dv = 1; cimglist_for(*this,l) dx = cimg::max(dx,(unsigned int)(*this)[l].size()); } break;
        case 'y': { dx = dz = dv = 1; cimglist_for(*this,l) dy+=(*this)[l].size(); } break;
        case 'z': { dy = size; dx = dv = 1; cimglist_for(*this,l) dz = cimg::max(dz,(unsigned int)(*this)[l].size()); } break;
        case 'v': { dy = size; dx = dz = 1; cimglist_for(*this,l) dv = cimg::max(dv,(unsigned int)(*this)[l].size()); } break;
        default:
          cimglist_for(*this,l) {
            const CImg<T>& img = (*this)[l];
            dx = cimg::max(dx,img.width);
            dy += img.height;
            dz = cimg::max(dz,img.depth);
            dv = cimg::max(dv,img.dim);
          }
        }
        res.assign(dx,dy,dz,dv,0);
        switch (cimg::uncase(align)) {
        case 'x': {
          cimglist_for(*this,l) res.draw_image(CImg<T>((*this)[l],true).unroll('x'),0,pos++,0,0);
        } break;
        case 'y': {
          cimglist_for(*this,l) { res.draw_image(CImg<T>((*this)[l],true).unroll('y'),0,pos,0,0); pos+=(*this)[l].size(); }
        } break;
        case 'z': {
          cimglist_for(*this,l) res.draw_image(CImg<T>((*this)[l],true).unroll('z'),0,pos++,0,0);
        } break;
        case 'v': {
          cimglist_for(*this,l) res.draw_image(CImg<T>((*this)[l],true).unroll('v'),0,pos++,0,0);
        } break;
        case 'p': {
          cimglist_for(*this,l) { res.draw_image((*this)[l],0,pos,0,0); pos+=(*this)[l].height; }
        } break;
        case 'n': {
          cimglist_for(*this,l) { res.draw_image((*this)[l],dx-(*this)[l].width,pos,dz-(*this)[l].depth,dv-(*this)[l].dim); pos+=(*this)[l].height; }
        } break;
        default : {
          cimglist_for(*this,l) { res.draw_image((*this)[l],(dx-(*this)[l].width)/2,pos,(dz-(*this)[l].depth)/2,(dv-(*this)[l].dim)/2);
          pos+=(*this)[l].height; }
        } break;
        }
      } break;

      case 'z': {
        switch (cimg::uncase(align)) {
        case 'x': { dz = size; dy = dv = 1; cimglist_for(*this,l) dx = cimg::max(dx,(unsigned int)(*this)[l].size()); } break;
        case 'y': { dz = size; dx = dv = 1; cimglist_for(*this,l) dy = cimg::max(dz,(unsigned int)(*this)[l].size()); } break;
        case 'z': { dx = dy = dv = 1; cimglist_for(*this,l) dz+=(*this)[l].size(); } break;
        case 'v': { dz = size; dx = dz = 1; cimglist_for(*this,l) dv = cimg::max(dv,(unsigned int)(*this)[l].size()); } break;
        default :
          cimglist_for(*this,l) {
            const CImg<T>& img = (*this)[l];
            dx = cimg::max(dx,img.width);
            dy = cimg::max(dy,img.height);
            dz += img.depth;
            dv = cimg::max(dv,img.dim);
          }
        }
        res.assign(dx,dy,dz,dv,0);
        switch (cimg::uncase(align)) {
        case 'x': {
          cimglist_for(*this,l) res.draw_image(CImg<T>((*this)[l],true).unroll('x'),0,0,pos++,0);
        } break;
        case 'y': {
          cimglist_for(*this,l) res.draw_image(CImg<T>((*this)[l],true).unroll('y'),0,0,pos++,0);
        } break;
        case 'z': {
          cimglist_for(*this,l) { res.draw_image(CImg<T>((*this)[l],true).unroll('z'),0,0,pos,0); pos+=(*this)[l].size(); }
        } break;
        case 'v': {
          cimglist_for(*this,l) res.draw_image(CImg<T>((*this)[l],true).unroll('v'),0,0,pos++,0);
        } break;
        case 'p': {
          cimglist_for(*this,l) { res.draw_image((*this)[l],0,0,pos,0); pos+=(*this)[l].depth; }
        } break;
        case 'n': {
          cimglist_for(*this,l) { res.draw_image((*this)[l],dx-(*this)[l].width,dy-(*this)[l].height,pos,dv-(*this)[l].dim); pos+=(*this)[l].depth; }
        } break;
        case 'c': {
          cimglist_for(*this,l) { res.draw_image((*this)[l],(dx-(*this)[l].width)/2,(dy-(*this)[l].height)/2,pos,(dv-(*this)[l].dim)/2);
          pos+=(*this)[l].depth; }
        } break;
        }
      } break;

      case 'v': {
        switch (cimg::uncase(align)) {
        case 'x': { dv = size; dy = dv = 1; cimglist_for(*this,l) dx = cimg::max(dx,(unsigned int)(*this)[l].size()); } break;
        case 'y': { dv = size; dx = dv = 1; cimglist_for(*this,l) dy = cimg::max(dz,(unsigned int)(*this)[l].size()); } break;
        case 'z': { dv = size; dx = dv = 1; cimglist_for(*this,l) dz = cimg::max(dv,(unsigned int)(*this)[l].size()); } break;
        case 'v': { dx = dy = dz = 1; cimglist_for(*this,l) dv+=(*this)[l].size(); } break;
        default :
          cimglist_for(*this,l) {
            const CImg<T>& img = (*this)[l];
            dx = cimg::max(dx,img.width);
            dy = cimg::max(dy,img.height);
            dz = cimg::max(dz,img.depth);
            dv += img.dim;
          }
        }
        res.assign(dx,dy,dz,dv,0);
        switch (cimg::uncase(align)) {
        case 'x': {
          cimglist_for(*this,l) res.draw_image(CImg<T>((*this)[l],true).unroll('x'),0,0,0,pos++);
        } break;
        case 'y': {
          cimglist_for(*this,l) res.draw_image(CImg<T>((*this)[l],true).unroll('y'),0,0,0,pos++);
        } break;
        case 'z': {
          cimglist_for(*this,l) res.draw_image(CImg<T>((*this)[l],true).unroll('v'),0,0,0,pos++);
        } break;
        case 'v': {
          cimglist_for(*this,l) { res.draw_image(CImg<T>((*this)[l],true).unroll('z'),0,0,0,pos); pos+=(*this)[l].size(); }
        } break;
        case 'p': {
          cimglist_for(*this,l) { res.draw_image((*this)[l],0,0,0,pos); pos+=(*this)[l].dim; }
        } break;
        case 'n': {
          cimglist_for(*this,l) { res.draw_image((*this)[l],dx-(*this)[l].width,dy-(*this)[l].height,dz-(*this)[l].depth,pos); pos+=(*this)[l].dim; }
        } break;
        case 'c': {
          cimglist_for(*this,l) { res.draw_image((*this)[l],(dx-(*this)[l].width)/2,(dy-(*this)[l].height)/2,(dz-(*this)[l].depth)/2,pos);
          pos+=(*this)[l].dim; }
        } break;
        }
      } break;
      default: throw CImgArgumentException("CImgList<%s>::get_append() : unknown axe '%c', must be 'x','y','z' or 'v'",pixel_type(),axe);
      }
      return res;
    }

    // Create an auto-cropped font (along the X axis) from a input font \p 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) res.insert(CImg<T>(letter.width,letter.height,1,letter.dim,0));
        else res.insert(letter.get_crop(xmin,0,xmax,letter.height-1));
      }
      res[' '].resize(res['f'].width);
      res[' '+256].resize(res['f'].width);
      return res;
    }

    //! In-place version of the previous function.
    CImgList<T>& crop_font() {
      return get_crop_font().transfer_to(*this);
    }

    // Get a font as an image list.
    static CImgList<T> get_font(const unsigned int *const font, const unsigned int w, const unsigned int h,
                                const unsigned int paddingx, const unsigned int paddingy, const bool variable_size=true) {
      CImgList<T> res = CImgList<T>(256,w,h,1,3).insert(CImgList<T>(256,w,h,1,1));
      const unsigned int *ptr = font;
      unsigned int m = 0, val = 0;
      for (unsigned int y=0; y<h; ++y)
        for (unsigned int x=0; x<256*w; ++x) {
          m>>=1; if (!m) { m = 0x80000000; val = *(ptr++); }
          CImg<T>& img = res[x/w], &mask = res[x/w+256];
          unsigned int xm = x%w;
          img(xm,y,0) = img(xm,y,1) = img(xm,y,2) = mask(xm,y,0) = (T)((val&m)?1:0);
        }
      if (variable_size) res.crop_font();
      if (paddingx || paddingy) cimglist_for(res,l) res[l].resize(res[l].dimx()+paddingx, res[l].dimy()+paddingy,1,-100,0);
      return res;
    }

    //! In-place version of the previous function.
    CImgList<T>& font(const unsigned int *const font, const unsigned int w, const unsigned int h,
                      const unsigned int paddingx, const unsigned int paddingy, const bool variable_size=true) {
      return get_font(font,w,h,paddingx,paddingy,variable_size).transfer_to(*this);
    }

    //! Return a CImg pre-defined font with desired size
    /**
       \param font_height = height of the desired font (can be 11,13,24,38 or 57)
       \param fixed_size = tell if the font has a fixed or variable width.
    **/
    static CImgList<T> get_font(const unsigned int font_width, const bool variable_size=true) {
      if (font_width<=11) {
        static CImgList<T> font7x11, nfont7x11;
        if (!variable_size && !font7x11)  font7x11  = get_font(cimg::font7x11,7,11,1,0,false);
        if (variable_size  && !nfont7x11) nfont7x11 = get_font(cimg::font7x11,7,11,1,0,true);
        return variable_size?nfont7x11:font7x11;
      }
      if (font_width<=13) {
        static CImgList<T> font10x13, nfont10x13;
        if (!variable_size && !font10x13)  font10x13  = get_font(cimg::font10x13,10,13,1,0,false);
        if (variable_size  && !nfont10x13) nfont10x13 = get_font(cimg::font10x13,10,13,1,0,true);
        return variable_size?nfont10x13:font10x13;
      }
      if (font_width<=17) {
        static CImgList<T> font8x17, nfont8x17;
        if (!variable_size && !font8x17)  font8x17  = get_font(cimg::font8x17,8,17,1,0,false);
        if (variable_size  && !nfont8x17) nfont8x17 = get_font(cimg::font8x17,8,17,1,0,true);
        return variable_size?nfont8x17:font8x17;
      }
      if (font_width<=19) {
        static CImgList<T> font10x19, nfont10x19;
        if (!variable_size && !font10x19)  font10x19  = get_font(cimg::font10x19,10,19,2,0,false);
        if (variable_size  && !nfont10x19) nfont10x19 = get_font(cimg::font10x19,10,19,2,0,true);
        return variable_size?nfont10x19:font10x19;
      }
      if (font_width<=24) {
        static CImgList<T> font12x24, nfont12x24;
        if (!variable_size && !font12x24)  font12x24  = get_font(cimg::font12x24,12,24,2,0,false);
        if (variable_size  && !nfont12x24) nfont12x24 = get_font(cimg::font12x24,12,24,2,0,true);
        return variable_size?nfont12x24:font12x24;
      }
      if (font_width<=32) {
        static CImgList<T> font16x32, nfont16x32;
        if (!variable_size && !font16x32)  font16x32  = get_font(cimg::font16x32,16,32,2,0,false);
        if (variable_size  && !nfont16x32) nfont16x32 = get_font(cimg::font16x32,16,32,2,0,true);
        return variable_size?nfont16x32:font16x32;
      }
      if (font_width<=38) {
        static CImgList<T> font19x38, nfont19x38;
        if (!variable_size && !font19x38)  font19x38  = get_font(cimg::font19x38,19,38,3,0,false);
        if (variable_size  && !nfont19x38) nfont19x38 = get_font(cimg::font19x38,19,38,3,0,true);
        return variable_size?nfont19x38:font19x38;
      }
      static CImgList<T> font29x57, nfont29x57;
      if (!variable_size && !font29x57)  font29x57  = get_font(cimg::font29x57,29,57,5,0,false);
      if (variable_size  && !nfont29x57) nfont29x57 = get_font(cimg::font29x57,29,57,5,0,true);
      return variable_size?nfont29x57:font29x57;
    }

    //! In-place version of the previous function.
    CImgList<T>& font(const unsigned int font_width, const bool variable_size=true) {
      return get_font(font_width,variable_size).transfer_to(*this);
    }

    //! Display the current CImgList instance in an existing CImgDisplay window (by reference).
    /**
       This function displays the list images of the current CImgList instance into an existing CImgDisplay window.
       Images of the list are concatenated in a single temporarly image for visualization purposes.
       The function returns immediately.
       \param disp : reference to an existing CImgDisplay instance, where the current image list will be displayed.
       \param axe : specify the axe for image concatenation. Can be 'x','y','z' or 'v'.
       \param align : specify the alignment for image concatenation. Can be 'p' (top), 'c' (center) or 'n' (bottom).
       \return A reference to the current CImgList instance is returned.
    **/
    const CImgList<T>& display(CImgDisplay& disp, const char axe='x', const char align='c') const {
      get_append(axe,align).display(disp);
      return *this;
    }

    //! Display the current CImgList instance in a new display window.
    /**
       This function opens a new window with a specific title and displays the list images of the current CImgList instance into it.
       Images of the list are concatenated in a single temporarly image for visualization purposes.
       The function returns when a key is pressed or the display window is closed by the user.
       \param title : specify the title of the opening display window.
       \param axe : specify the axe for image concatenation. Can be 'x','y','z' or 'v'.
       \param align : specify the alignment for image concatenation. Can be 'p' (top), 'c' (center) or 'n' (bottom).
       \return A reference to the current CImgList instance is returned.
    **/
    const CImgList<T>& display(const char* title=0, const bool display_info=true, const char axe='x', const char align='c') const {
      if (is_empty())
        throw CImgInstanceException("CImgList<%s>::display() : Instance list (%u,%u) is empty.",
                                    pixel_type(),size,data);
      const CImg<T> visu = get_append(axe,align);
      char ntitle[64] = { 0 };
      if (!title) std::sprintf(ntitle,"CImgList<%s>",pixel_type());
      if (display_info) print(title?title:ntitle);
      visu.display(title?title:ntitle,false);
      return *this;
    }

   };

  /*
   #-----------------------------------------
   #
   #
   #
   # Complete previously defined functions
   #
   #
   #
   #------------------------------------------
  */

namespace cimg {

  //! Display a dialog box, where a user can click standard buttons.
  /**
     Up to 6 buttons can be defined in the dialog window.
     This function returns when a user clicked one of the button or closed the dialog window.
     \param title = Title of the dialog window.
     \param msg = Main message displayed inside the dialog window.
     \param button1_txt = Label of the 1st button.
     \param button2_txt = Label of the 2nd button.
     \param button3_txt = Label of the 3rd button.
     \param button4_txt = Label of the 4th button.
     \param button5_txt = Label of the 5th button.
     \param button6_txt = Label of the 6th button.
     \param logo = Logo image displayed at the left of the main message. This parameter is optional.
     \param centering = Tell to center the dialog window on the screen.
     \return The button number (from 0 to 5), or -1 if the dialog window has been closed by the user.
     \note If a button text is set to 0, then the corresponding button (and the followings) won't appear in
     the dialog box. At least one button is necessary.
  **/

  template<typename t>
  inline int dialog(const char *title, const char *msg,
                    const char *button1_txt, const char *button2_txt,
                    const char *button3_txt, const char *button4_txt,
                    const char *button5_txt, const char *button6_txt,
                    const CImg<t>& logo, const bool centering = false) {
#if cimg_display_type!=0
    const unsigned char
      black[] = { 0,0,0 }, white[] = { 255,255,255 }, gray[] = { 200,200,200 }, gray2[] = { 150,150,150 };

      // Create buttons and canvas graphics
      CImgList<unsigned char> buttons, cbuttons, sbuttons;
      if (button1_txt) { buttons.insert(CImg<unsigned char>().draw_text(button1_txt,0,0,black,gray,13));
      if (button2_txt) { buttons.insert(CImg<unsigned char>().draw_text(button2_txt,0,0,black,gray,13));
      if (button3_txt) { buttons.insert(CImg<unsigned char>().draw_text(button3_txt,0,0,black,gray,13));
      if (button4_txt) { buttons.insert(CImg<unsigned char>().draw_text(button4_txt,0,0,black,gray,13));
      if (button5_txt) { buttons.insert(CImg<unsigned char>().draw_text(button5_txt,0,0,black,gray,13));
      if (button6_txt) { buttons.insert(CImg<unsigned char>().draw_text(button6_txt,0,0,black,gray,13));
      }}}}}}
      if (!buttons.size) throw CImgArgumentException("cimg::dialog() : No buttons have been defined. At least one is necessary");

      unsigned int bw = 0, bh = 0;
      cimglist_for(buttons,l) { bw = cimg::max(bw,buttons[l].width); bh = cimg::max(bh,buttons[l].height); }
      bw+=8; bh+=8;
      if (bw<64) bw=64;
      if (bw>128) bw=128;
      if (bh<24) bh=24;
      if (bh>48) bh=48;

      CImg<unsigned char> button(bw,bh,1,3);
      button.draw_rectangle(0,0,bw-1,bh-1,gray);
      button.draw_line(0,0,bw-1,0,white).draw_line(0,bh-1,0,0,white);
      button.draw_line(bw-1,0,bw-1,bh-1,black).draw_line(bw-1,bh-1,0,bh-1,black);
      button.draw_line(1,bh-2,bw-2,bh-2,gray2).draw_line(bw-2,bh-2,bw-2,1,gray2);
      CImg<unsigned char> sbutton(bw,bh,1,3);
      sbutton.draw_rectangle(0,0,bw-1,bh-1,gray);
      sbutton.draw_line(0,0,bw-1,0,black).draw_line(bw-1,0,bw-1,bh-1,black);
      sbutton.draw_line(bw-1,bh-1,0,bh-1,black).draw_line(0,bh-1,0,0,black);
      sbutton.draw_line(1,1,bw-2,1,white).draw_line(1,bh-2,1,1,white);
      sbutton.draw_line(bw-2,1,bw-2,bh-2,black).draw_line(bw-2,bh-2,1,bh-2,black);
      sbutton.draw_line(2,bh-3,bw-3,bh-3,gray2).draw_line(bw-3,bh-3,bw-3,2,gray2);
      sbutton.draw_line(4,4,bw-5,4,black,1.0f,0xAAAAAAAA,true).draw_line(bw-5,4,bw-5,bh-5,black,1.0f,0xAAAAAAAA,false);
      sbutton.draw_line(bw-5,bh-5,4,bh-5,black,1.0f,0xAAAAAAAA,false).draw_line(4,bh-5,4,4,black,1.0f,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.0f,0xAAAAAAAA,true).draw_line(bw-5,4,bw-5,bh-5,black,1.0f,0xAAAAAAAA,false);
      cbutton.draw_line(bw-5,bh-5,4,bh-5,black,1.0f,0xAAAAAAAA,false).draw_line(4,bh-5,4,4,black,1.0f,0xAAAAAAAA,false);

      cimglist_for(buttons,ll) {
        cbuttons.insert(CImg<unsigned char>(cbutton).draw_image(buttons[ll],1+(bw-buttons[ll].dimx())/2,1+(bh-buttons[ll].dimy())/2));
        sbuttons.insert(CImg<unsigned char>(sbutton).draw_image(buttons[ll],(bw-buttons[ll].dimx())/2,(bh-buttons[ll].dimy())/2));
        buttons[ll] = CImg<unsigned char>(button).draw_image(buttons[ll],(bw-buttons[ll].dimx())/2,(bh-buttons[ll].dimy())/2);
      }

      CImg<unsigned char> canvas;
      if (msg) canvas = CImg<unsigned char>().draw_text(msg,0,0,black,gray,13);
      const unsigned int
        bwall = (buttons.size-1)*(12+bw) + bw,
        w = cimg::max(196U,36+logo.width+canvas.width, 24+bwall),
        h = cimg::max(96U,36+canvas.height+bh,36+logo.height+bh),
        lx = 12 + (canvas.data?0:((w-24-logo.width)/2)),
        ly = (h-12-bh-logo.height)/2,
        tx = lx+logo.width+12,
        ty = (h-12-bh-canvas.height)/2,
        bx = (w-bwall)/2,
        by = h-12-bh;

      if (canvas.data)
        canvas = CImg<unsigned char>(w,h,1,3).
          draw_rectangle(0,0,w-1,h-1,gray).
          draw_line(0,0,w-1,0,white).draw_line(0,h-1,0,0,white).
          draw_line(w-1,0,w-1,h-1,black).draw_line(w-1,h-1,0,h-1,black).
          draw_image(canvas,tx,ty);
      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(logo,lx,ly);

      unsigned int xbuttons[6];
      cimglist_for(buttons,lll) { xbuttons[lll] = bx+(bw+12)*lll; canvas.draw_image(buttons[lll],xbuttons[lll],by); }

      // Open window and enter events loop
      CImgDisplay disp(canvas,title?title:" ",0,3,false,centering?true:false);
      if (centering) disp.move((CImgDisplay::screen_dimx()-disp.dimx())/2,
                               (CImgDisplay::screen_dimy()-disp.dimy())/2);
      bool stopflag = false, refresh = false;
      int oselected = -1, oclicked = -1, selected = -1, clicked = -1;
      while (!disp.is_closed && !stopflag) {
        if (refresh) {
          if (clicked>=0) CImg<unsigned char>(canvas).draw_image(cbuttons[clicked],xbuttons[clicked],by).display(disp);
          else {
            if (selected>=0) CImg<unsigned char>(canvas).draw_image(sbuttons[selected],xbuttons[selected],by).display(disp);
            else canvas.display(disp);
          }
          refresh = false;
        }
        disp.wait(15);
        if (disp.is_resized) disp.resize(disp);

        if (disp.button&1)  {
          oclicked = clicked;
          clicked = -1;
          cimglist_for(buttons,l)
            if (disp.mouse_y>=(int)by && disp.mouse_y<(int)(by+bh) &&
                disp.mouse_x>=(int)xbuttons[l] && disp.mouse_x<(int)(xbuttons[l]+bw)) {
              clicked = selected = l;
              refresh = true;
            }
          if (clicked!=oclicked) refresh = true;
        } else if (clicked>=0) stopflag = true;

        if (disp.key) {
          oselected = selected;
          switch (disp.key) {
          case cimg::keyESC: selected=-1; stopflag=true; break;
          case cimg::keyENTER: if (selected<0) selected = 0; stopflag = true; break;
          case cimg::keyTAB:
          case cimg::keyARROWRIGHT:
          case cimg::keyARROWDOWN: selected = (selected+1)%buttons.size; break;
          case cimg::keyARROWLEFT:
          case cimg::keyARROWUP: selected = (selected+buttons.size-1)%buttons.size; break;
          }
          disp.key = 0;
          if (selected!=oselected) refresh = true;
        }
      }
      if (!disp) selected = -1;
      return selected;
#else
      std::fprintf(stderr,"<%s>\n\n%s\n\n",title,msg);
      return -1+0*(int)(button1_txt-button2_txt+button3_txt-button4_txt+button5_txt-button6_txt+logo.width+(int)centering);
#endif
  }

  inline int dialog(const char *title, const char *msg,
                    const char *button1_txt, const char *button2_txt, const char *button3_txt,
                    const char *button4_txt, const char *button5_txt, const char *button6_txt,
                    const bool centering) {
    return dialog(title,msg,button1_txt,button2_txt,button3_txt,button4_txt,button5_txt,button6_txt,
                  CImg<unsigned char>::get_logo40x38(),centering);
  }


  // Inner routine used by the Marching cube algorithm
  template<typename t> inline int _marching_cubes_indice(const unsigned int edge, const CImg<t>& indices1, const CImg<t>& indices2,
                                                         const unsigned int x, const unsigned int y, const unsigned int nx, const unsigned int ny) {
    switch (edge) {
    case 0: return indices1(x,y,0);
    case 1: return indices1(nx,y,1);
    case 2: return indices1(x,ny,0);
    case 3: return indices1(x,y,1);
    case 4: return indices2(x,y,0);
    case 5: return indices2(nx,y,1);
    case 6: return indices2(x,ny,0);
    case 7: return indices2(x,y,1);
    case 8: return indices1(x,y,2);
    case 9: return indices1(nx,y,2);
    case 10: return indices1(nx,ny,2);
    case 11: return indices1(x,ny,2);
    }
    return 0;
  }

  //! Polygonize an implicit function
  // This function uses the Marching Cubes Tables published on the web page :
  // http://astronomy.swin.edu.au/~pbourke/modelling/polygonise/
  template<typename tfunc, typename tp, typename tf>
  inline void marching_cubes(const tfunc& func, const float isovalue,
                             const float x0, const float y0, const float z0,
                             const float x1, const float y1, const float z1,
                             const float resx, const float resy, const float resz,
                             CImgList<tp>& points, CImgList<tf>& primitives,
                             const bool invert_faces) {

    static unsigned int edges[256] = {
      0x000, 0x109, 0x203, 0x30a, 0x406, 0x50f, 0x605, 0x70c, 0x80c, 0x905, 0xa0f, 0xb06, 0xc0a, 0xd03, 0xe09, 0xf00,
      0x190, 0x99 , 0x393, 0x29a, 0x596, 0x49f, 0x795, 0x69c, 0x99c, 0x895, 0xb9f, 0xa96, 0xd9a, 0xc93, 0xf99, 0xe90,
      0x230, 0x339, 0x33 , 0x13a, 0x636, 0x73f, 0x435, 0x53c, 0xa3c, 0xb35, 0x83f, 0x936, 0xe3a, 0xf33, 0xc39, 0xd30,
      0x3a0, 0x2a9, 0x1a3, 0xaa , 0x7a6, 0x6af, 0x5a5, 0x4ac, 0xbac, 0xaa5, 0x9af, 0x8a6, 0xfaa, 0xea3, 0xda9, 0xca0,
      0x460, 0x569, 0x663, 0x76a, 0x66 , 0x16f, 0x265, 0x36c, 0xc6c, 0xd65, 0xe6f, 0xf66, 0x86a, 0x963, 0xa69, 0xb60,
      0x5f0, 0x4f9, 0x7f3, 0x6fa, 0x1f6, 0xff , 0x3f5, 0x2fc, 0xdfc, 0xcf5, 0xfff, 0xef6, 0x9fa, 0x8f3, 0xbf9, 0xaf0,
      0x650, 0x759, 0x453, 0x55a, 0x256, 0x35f, 0x55 , 0x15c, 0xe5c, 0xf55, 0xc5f, 0xd56, 0xa5a, 0xb53, 0x859, 0x950,
      0x7c0, 0x6c9, 0x5c3, 0x4ca, 0x3c6, 0x2cf, 0x1c5, 0xcc , 0xfcc, 0xec5, 0xdcf, 0xcc6, 0xbca, 0xac3, 0x9c9, 0x8c0,
      0x8c0, 0x9c9, 0xac3, 0xbca, 0xcc6, 0xdcf, 0xec5, 0xfcc, 0xcc , 0x1c5, 0x2cf, 0x3c6, 0x4ca, 0x5c3, 0x6c9, 0x7c0,
      0x950, 0x859, 0xb53, 0xa5a, 0xd56, 0xc5f, 0xf55, 0xe5c, 0x15c, 0x55 , 0x35f, 0x256, 0x55a, 0x453, 0x759, 0x650,
      0xaf0, 0xbf9, 0x8f3, 0x9fa, 0xef6, 0xfff, 0xcf5, 0xdfc, 0x2fc, 0x3f5, 0xff , 0x1f6, 0x6fa, 0x7f3, 0x4f9, 0x5f0,
      0xb60, 0xa69, 0x963, 0x86a, 0xf66, 0xe6f, 0xd65, 0xc6c, 0x36c, 0x265, 0x16f, 0x66 , 0x76a, 0x663, 0x569, 0x460,
      0xca0, 0xda9, 0xea3, 0xfaa, 0x8a6, 0x9af, 0xaa5, 0xbac, 0x4ac, 0x5a5, 0x6af, 0x7a6, 0xaa , 0x1a3, 0x2a9, 0x3a0,
      0xd30, 0xc39, 0xf33, 0xe3a, 0x936, 0x83f, 0xb35, 0xa3c, 0x53c, 0x435, 0x73f, 0x636, 0x13a, 0x33 , 0x339, 0x230,
      0xe90, 0xf99, 0xc93, 0xd9a, 0xa96, 0xb9f, 0x895, 0x99c, 0x69c, 0x795, 0x49f, 0x596, 0x29a, 0x393, 0x99 , 0x190,
      0xf00, 0xe09, 0xd03, 0xc0a, 0xb06, 0xa0f, 0x905, 0x80c, 0x70c, 0x605, 0x50f, 0x406, 0x30a, 0x203, 0x109, 0x000 };

    static int triangles[256][16] =
      {{-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {0, 8, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
       {0, 1, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {1, 8, 3, 9, 8, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
       {1, 2, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {0, 8, 3, 1, 2, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
       {9, 2, 10, 0, 2, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {2, 8, 3, 2, 10, 8, 10, 9, 8, -1, -1, -1, -1, -1, -1, -1},
       {3, 11, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {0, 11, 2, 8, 11, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
       {1, 9, 0, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {1, 11, 2, 1, 9, 11, 9, 8, 11, -1, -1, -1, -1, -1, -1, -1},
       {3, 10, 1, 11, 10, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {0, 10, 1, 0, 8, 10, 8, 11, 10, -1, -1, -1, -1, -1, -1, -1},
       {3, 9, 0, 3, 11, 9, 11, 10, 9, -1, -1, -1, -1, -1, -1, -1}, {9, 8, 10, 10, 8, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
       {4, 7, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {4, 3, 0, 7, 3, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
       {0, 1, 9, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {4, 1, 9, 4, 7, 1, 7, 3, 1, -1, -1, -1, -1, -1, -1, -1},
       {1, 2, 10, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {3, 4, 7, 3, 0, 4, 1, 2, 10, -1, -1, -1, -1, -1, -1, -1},
       {9, 2, 10, 9, 0, 2, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1}, {2, 10, 9, 2, 9, 7, 2, 7, 3, 7, 9, 4, -1, -1, -1, -1},
       {8, 4, 7, 3, 11, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {11, 4, 7, 11, 2, 4, 2, 0, 4, -1, -1, -1, -1, -1, -1, -1},
       {9, 0, 1, 8, 4, 7, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1}, {4, 7, 11, 9, 4, 11, 9, 11, 2, 9, 2, 1, -1, -1, -1, -1},
       {3, 10, 1, 3, 11, 10, 7, 8, 4, -1, -1, -1, -1, -1, -1, -1}, {1, 11, 10, 1, 4, 11, 1, 0, 4, 7, 11, 4, -1, -1, -1, -1},
       {4, 7, 8, 9, 0, 11, 9, 11, 10, 11, 0, 3, -1, -1, -1, -1}, {4, 7, 11, 4, 11, 9, 9, 11, 10, -1, -1, -1, -1, -1, -1, -1},
       {9, 5, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {9, 5, 4, 0, 8, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
       {0, 5, 4, 1, 5, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {8, 5, 4, 8, 3, 5, 3, 1, 5, -1, -1, -1, -1, -1, -1, -1},
       {1, 2, 10, 9, 5, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {3, 0, 8, 1, 2, 10, 4, 9, 5, -1, -1, -1, -1, -1, -1, -1},
       {5, 2, 10, 5, 4, 2, 4, 0, 2, -1, -1, -1, -1, -1, -1, -1}, {2, 10, 5, 3, 2, 5, 3, 5, 4, 3, 4, 8, -1, -1, -1, -1},
       {9, 5, 4, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {0, 11, 2, 0, 8, 11, 4, 9, 5, -1, -1, -1, -1, -1, -1, -1},
       {0, 5, 4, 0, 1, 5, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1}, {2, 1, 5, 2, 5, 8, 2, 8, 11, 4, 8, 5, -1, -1, -1, -1},
       {10, 3, 11, 10, 1, 3, 9, 5, 4, -1, -1, -1, -1, -1, -1, -1}, {4, 9, 5, 0, 8, 1, 8, 10, 1, 8, 11, 10, -1, -1, -1, -1},
       {5, 4, 0, 5, 0, 11, 5, 11, 10, 11, 0, 3, -1, -1, -1, -1}, {5, 4, 8, 5, 8, 10, 10, 8, 11, -1, -1, -1, -1, -1, -1, -1},
       {9, 7, 8, 5, 7, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {9, 3, 0, 9, 5, 3, 5, 7, 3, -1, -1, -1, -1, -1, -1, -1},
       {0, 7, 8, 0, 1, 7, 1, 5, 7, -1, -1, -1, -1, -1, -1, -1}, {1, 5, 3, 3, 5, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
       {9, 7, 8, 9, 5, 7, 10, 1, 2, -1, -1, -1, -1, -1, -1, -1}, {10, 1, 2, 9, 5, 0, 5, 3, 0, 5, 7, 3, -1, -1, -1, -1},
       {8, 0, 2, 8, 2, 5, 8, 5, 7, 10, 5, 2, -1, -1, -1, -1}, {2, 10, 5, 2, 5, 3, 3, 5, 7, -1, -1, -1, -1, -1, -1, -1},
       {7, 9, 5, 7, 8, 9, 3, 11, 2, -1, -1, -1, -1, -1, -1, -1}, {9, 5, 7, 9, 7, 2, 9, 2, 0, 2, 7, 11, -1, -1, -1, -1},
       {2, 3, 11, 0, 1, 8, 1, 7, 8, 1, 5, 7, -1, -1, -1, -1}, {11, 2, 1, 11, 1, 7, 7, 1, 5, -1, -1, -1, -1, -1, -1, -1},
       {9, 5, 8, 8, 5, 7, 10, 1, 3, 10, 3, 11, -1, -1, -1, -1}, {5, 7, 0, 5, 0, 9, 7, 11, 0, 1, 0, 10, 11, 10, 0, -1},
       {11, 10, 0, 11, 0, 3, 10, 5, 0, 8, 0, 7, 5, 7, 0, -1}, {11, 10, 5, 7, 11, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
       {10, 6, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {0, 8, 3, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
       {9, 0, 1, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {1, 8, 3, 1, 9, 8, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1},
       {1, 6, 5, 2, 6, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {1, 6, 5, 1, 2, 6, 3, 0, 8, -1, -1, -1, -1, -1, -1, -1},
       {9, 6, 5, 9, 0, 6, 0, 2, 6, -1, -1, -1, -1, -1, -1, -1}, {5, 9, 8, 5, 8, 2, 5, 2, 6, 3, 2, 8, -1, -1, -1, -1},
       {2, 3, 11, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {11, 0, 8, 11, 2, 0, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1},
       {0, 1, 9, 2, 3, 11, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1}, {5, 10, 6, 1, 9, 2, 9, 11, 2, 9, 8, 11, -1, -1, -1, -1},
       {6, 3, 11, 6, 5, 3, 5, 1, 3, -1, -1, -1, -1, -1, -1, -1}, {0, 8, 11, 0, 11, 5, 0, 5, 1, 5, 11, 6, -1, -1, -1, -1},
       {3, 11, 6, 0, 3, 6, 0, 6, 5, 0, 5, 9, -1, -1, -1, -1}, {6, 5, 9, 6, 9, 11, 11, 9, 8, -1, -1, -1, -1, -1, -1, -1},
       {5, 10, 6, 4, 7, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {4, 3, 0, 4, 7, 3, 6, 5, 10, -1, -1, -1, -1, -1, -1, -1},
       {1, 9, 0, 5, 10, 6, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1}, {10, 6, 5, 1, 9, 7, 1, 7, 3, 7, 9, 4, -1, -1, -1, -1},
       {6, 1, 2, 6, 5, 1, 4, 7, 8, -1, -1, -1, -1, -1, -1, -1}, {1, 2, 5, 5, 2, 6, 3, 0, 4, 3, 4, 7, -1, -1, -1, -1},
       {8, 4, 7, 9, 0, 5, 0, 6, 5, 0, 2, 6, -1, -1, -1, -1}, {7, 3, 9, 7, 9, 4, 3, 2, 9, 5, 9, 6, 2, 6, 9, -1},
       {3, 11, 2, 7, 8, 4, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1}, {5, 10, 6, 4, 7, 2, 4, 2, 0, 2, 7, 11, -1, -1, -1, -1},
       {0, 1, 9, 4, 7, 8, 2, 3, 11, 5, 10, 6, -1, -1, -1, -1}, {9, 2, 1, 9, 11, 2, 9, 4, 11, 7, 11, 4, 5, 10, 6, -1},
       {8, 4, 7, 3, 11, 5, 3, 5, 1, 5, 11, 6, -1, -1, -1, -1}, {5, 1, 11, 5, 11, 6, 1, 0, 11, 7, 11, 4, 0, 4, 11, -1},
       {0, 5, 9, 0, 6, 5, 0, 3, 6, 11, 6, 3, 8, 4, 7, -1}, {6, 5, 9, 6, 9, 11, 4, 7, 9, 7, 11, 9, -1, -1, -1, -1},
       {10, 4, 9, 6, 4, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {4, 10, 6, 4, 9, 10, 0, 8, 3, -1, -1, -1, -1, -1, -1, -1},
       {10, 0, 1, 10, 6, 0, 6, 4, 0, -1, -1, -1, -1, -1, -1, -1}, {8, 3, 1, 8, 1, 6, 8, 6, 4, 6, 1, 10, -1, -1, -1, -1},
       {1, 4, 9, 1, 2, 4, 2, 6, 4, -1, -1, -1, -1, -1, -1, -1}, {3, 0, 8, 1, 2, 9, 2, 4, 9, 2, 6, 4, -1, -1, -1, -1},
       {0, 2, 4, 4, 2, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {8, 3, 2, 8, 2, 4, 4, 2, 6, -1, -1, -1, -1, -1, -1, -1},
       {10, 4, 9, 10, 6, 4, 11, 2, 3, -1, -1, -1, -1, -1, -1, -1}, {0, 8, 2, 2, 8, 11, 4, 9, 10, 4, 10, 6, -1, -1, -1, -1},
       {3, 11, 2, 0, 1, 6, 0, 6, 4, 6, 1, 10, -1, -1, -1, -1}, {6, 4, 1, 6, 1, 10, 4, 8, 1, 2, 1, 11, 8, 11, 1, -1},
       {9, 6, 4, 9, 3, 6, 9, 1, 3, 11, 6, 3, -1, -1, -1, -1}, {8, 11, 1, 8, 1, 0, 11, 6, 1, 9, 1, 4, 6, 4, 1, -1},
       {3, 11, 6, 3, 6, 0, 0, 6, 4, -1, -1, -1, -1, -1, -1, -1}, {6, 4, 8, 11, 6, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
       {7, 10, 6, 7, 8, 10, 8, 9, 10, -1, -1, -1, -1, -1, -1, -1}, {0, 7, 3, 0, 10, 7, 0, 9, 10, 6, 7, 10, -1, -1, -1, -1},
       {10, 6, 7, 1, 10, 7, 1, 7, 8, 1, 8, 0, -1, -1, -1, -1}, {10, 6, 7, 10, 7, 1, 1, 7, 3, -1, -1, -1, -1, -1, -1, -1},
       {1, 2, 6, 1, 6, 8, 1, 8, 9, 8, 6, 7, -1, -1, -1, -1}, {2, 6, 9, 2, 9, 1, 6, 7, 9, 0, 9, 3, 7, 3, 9, -1},
       {7, 8, 0, 7, 0, 6, 6, 0, 2, -1, -1, -1, -1, -1, -1, -1}, {7, 3, 2, 6, 7, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
       {2, 3, 11, 10, 6, 8, 10, 8, 9, 8, 6, 7, -1, -1, -1, -1}, {2, 0, 7, 2, 7, 11, 0, 9, 7, 6, 7, 10, 9, 10, 7, -1},
       {1, 8, 0, 1, 7, 8, 1, 10, 7, 6, 7, 10, 2, 3, 11, -1}, {11, 2, 1, 11, 1, 7, 10, 6, 1, 6, 7, 1, -1, -1, -1, -1},
       {8, 9, 6, 8, 6, 7, 9, 1, 6, 11, 6, 3, 1, 3, 6, -1}, {0, 9, 1, 11, 6, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
       {7, 8, 0, 7, 0, 6, 3, 11, 0, 11, 6, 0, -1, -1, -1, -1}, {7, 11, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
       {7, 6, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {3, 0, 8, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
       {0, 1, 9, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {8, 1, 9, 8, 3, 1, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1},
       {10, 1, 2, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {1, 2, 10, 3, 0, 8, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1},
       {2, 9, 0, 2, 10, 9, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1}, {6, 11, 7, 2, 10, 3, 10, 8, 3, 10, 9, 8, -1, -1, -1, -1},
       {7, 2, 3, 6, 2, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {7, 0, 8, 7, 6, 0, 6, 2, 0, -1, -1, -1, -1, -1, -1, -1},
       {2, 7, 6, 2, 3, 7, 0, 1, 9, -1, -1, -1, -1, -1, -1, -1}, {1, 6, 2, 1, 8, 6, 1, 9, 8, 8, 7, 6, -1, -1, -1, -1},
       {10, 7, 6, 10, 1, 7, 1, 3, 7, -1, -1, -1, -1, -1, -1, -1}, {10, 7, 6, 1, 7, 10, 1, 8, 7, 1, 0, 8, -1, -1, -1, -1},
       {0, 3, 7, 0, 7, 10, 0, 10, 9, 6, 10, 7, -1, -1, -1, -1}, {7, 6, 10, 7, 10, 8, 8, 10, 9, -1, -1, -1, -1, -1, -1, -1},
       {6, 8, 4, 11, 8, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {3, 6, 11, 3, 0, 6, 0, 4, 6, -1, -1, -1, -1, -1, -1, -1},
       {8, 6, 11, 8, 4, 6, 9, 0, 1, -1, -1, -1, -1, -1, -1, -1}, {9, 4, 6, 9, 6, 3, 9, 3, 1, 11, 3, 6, -1, -1, -1, -1},
       {6, 8, 4, 6, 11, 8, 2, 10, 1, -1, -1, -1, -1, -1, -1, -1}, {1, 2, 10, 3, 0, 11, 0, 6, 11, 0, 4, 6, -1, -1, -1, -1},
       {4, 11, 8, 4, 6, 11, 0, 2, 9, 2, 10, 9, -1, -1, -1, -1}, {10, 9, 3, 10, 3, 2, 9, 4, 3, 11, 3, 6, 4, 6, 3, -1},
       {8, 2, 3, 8, 4, 2, 4, 6, 2, -1, -1, -1, -1, -1, -1, -1}, {0, 4, 2, 4, 6, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
       {1, 9, 0, 2, 3, 4, 2, 4, 6, 4, 3, 8, -1, -1, -1, -1}, {1, 9, 4, 1, 4, 2, 2, 4, 6, -1, -1, -1, -1, -1, -1, -1},
       {8, 1, 3, 8, 6, 1, 8, 4, 6, 6, 10, 1, -1, -1, -1, -1}, {10, 1, 0, 10, 0, 6, 6, 0, 4, -1, -1, -1, -1, -1, -1, -1},
       {4, 6, 3, 4, 3, 8, 6, 10, 3, 0, 3, 9, 10, 9, 3, -1}, {10, 9, 4, 6, 10, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
       {4, 9, 5, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {0, 8, 3, 4, 9, 5, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1},
       {5, 0, 1, 5, 4, 0, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1}, {11, 7, 6, 8, 3, 4, 3, 5, 4, 3, 1, 5, -1, -1, -1, -1},
       {9, 5, 4, 10, 1, 2, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1}, {6, 11, 7, 1, 2, 10, 0, 8, 3, 4, 9, 5, -1, -1, -1, -1},
       {7, 6, 11, 5, 4, 10, 4, 2, 10, 4, 0, 2, -1, -1, -1, -1}, {3, 4, 8, 3, 5, 4, 3, 2, 5, 10, 5, 2, 11, 7, 6, -1},
       {7, 2, 3, 7, 6, 2, 5, 4, 9, -1, -1, -1, -1, -1, -1, -1}, {9, 5, 4, 0, 8, 6, 0, 6, 2, 6, 8, 7, -1, -1, -1, -1},
       {3, 6, 2, 3, 7, 6, 1, 5, 0, 5, 4, 0, -1, -1, -1, -1}, {6, 2, 8, 6, 8, 7, 2, 1, 8, 4, 8, 5, 1, 5, 8, -1},
       {9, 5, 4, 10, 1, 6, 1, 7, 6, 1, 3, 7, -1, -1, -1, -1}, {1, 6, 10, 1, 7, 6, 1, 0, 7, 8, 7, 0, 9, 5, 4, -1},
       {4, 0, 10, 4, 10, 5, 0, 3, 10, 6, 10, 7, 3, 7, 10, -1}, {7, 6, 10, 7, 10, 8, 5, 4, 10, 4, 8, 10, -1, -1, -1, -1},
       {6, 9, 5, 6, 11, 9, 11, 8, 9, -1, -1, -1, -1, -1, -1, -1}, {3, 6, 11, 0, 6, 3, 0, 5, 6, 0, 9, 5, -1, -1, -1, -1},
       {0, 11, 8, 0, 5, 11, 0, 1, 5, 5, 6, 11, -1, -1, -1, -1}, {6, 11, 3, 6, 3, 5, 5, 3, 1, -1, -1, -1, -1, -1, -1, -1},
       {1, 2, 10, 9, 5, 11, 9, 11, 8, 11, 5, 6, -1, -1, -1, -1}, {0, 11, 3, 0, 6, 11, 0, 9, 6, 5, 6, 9, 1, 2, 10, -1},
       {11, 8, 5, 11, 5, 6, 8, 0, 5, 10, 5, 2, 0, 2, 5, -1}, {6, 11, 3, 6, 3, 5, 2, 10, 3, 10, 5, 3, -1, -1, -1, -1},
       {5, 8, 9, 5, 2, 8, 5, 6, 2, 3, 8, 2, -1, -1, -1, -1}, {9, 5, 6, 9, 6, 0, 0, 6, 2, -1, -1, -1, -1, -1, -1, -1},
       {1, 5, 8, 1, 8, 0, 5, 6, 8, 3, 8, 2, 6, 2, 8, -1}, {1, 5, 6, 2, 1, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
       {1, 3, 6, 1, 6, 10, 3, 8, 6, 5, 6, 9, 8, 9, 6, -1}, {10, 1, 0, 10, 0, 6, 9, 5, 0, 5, 6, 0, -1, -1, -1, -1},
       {0, 3, 8, 5, 6, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {10, 5, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
       {11, 5, 10, 7, 5, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {11, 5, 10, 11, 7, 5, 8, 3, 0, -1, -1, -1, -1, -1, -1, -1},
       {5, 11, 7, 5, 10, 11, 1, 9, 0, -1, -1, -1, -1, -1, -1, -1}, {10, 7, 5, 10, 11, 7, 9, 8, 1, 8, 3, 1, -1, -1, -1, -1},
       {11, 1, 2, 11, 7, 1, 7, 5, 1, -1, -1, -1, -1, -1, -1, -1}, {0, 8, 3, 1, 2, 7, 1, 7, 5, 7, 2, 11, -1, -1, -1, -1},
       {9, 7, 5, 9, 2, 7, 9, 0, 2, 2, 11, 7, -1, -1, -1, -1}, {7, 5, 2, 7, 2, 11, 5, 9, 2, 3, 2, 8, 9, 8, 2, -1},
       {2, 5, 10, 2, 3, 5, 3, 7, 5, -1, -1, -1, -1, -1, -1, -1}, {8, 2, 0, 8, 5, 2, 8, 7, 5, 10, 2, 5, -1, -1, -1, -1},
       {9, 0, 1, 5, 10, 3, 5, 3, 7, 3, 10, 2, -1, -1, -1, -1}, {9, 8, 2, 9, 2, 1, 8, 7, 2, 10, 2, 5, 7, 5, 2, -1},
       {1, 3, 5, 3, 7, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {0, 8, 7, 0, 7, 1, 1, 7, 5, -1, -1, -1, -1, -1, -1, -1},
       {9, 0, 3, 9, 3, 5, 5, 3, 7, -1, -1, -1, -1, -1, -1, -1}, {9, 8, 7, 5, 9, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
       {5, 8, 4, 5, 10, 8, 10, 11, 8, -1, -1, -1, -1, -1, -1, -1}, {5, 0, 4, 5, 11, 0, 5, 10, 11, 11, 3, 0, -1, -1, -1, -1},
       {0, 1, 9, 8, 4, 10, 8, 10, 11, 10, 4, 5, -1, -1, -1, -1}, {10, 11, 4, 10, 4, 5, 11, 3, 4, 9, 4, 1, 3, 1, 4, -1},
       {2, 5, 1, 2, 8, 5, 2, 11, 8, 4, 5, 8, -1, -1, -1, -1}, {0, 4, 11, 0, 11, 3, 4, 5, 11, 2, 11, 1, 5, 1, 11, -1},
       {0, 2, 5, 0, 5, 9, 2, 11, 5, 4, 5, 8, 11, 8, 5, -1}, {9, 4, 5, 2, 11, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
       {2, 5, 10, 3, 5, 2, 3, 4, 5, 3, 8, 4, -1, -1, -1, -1}, {5, 10, 2, 5, 2, 4, 4, 2, 0, -1, -1, -1, -1, -1, -1, -1},
       {3, 10, 2, 3, 5, 10, 3, 8, 5, 4, 5, 8, 0, 1, 9, -1}, {5, 10, 2, 5, 2, 4, 1, 9, 2, 9, 4, 2, -1, -1, -1, -1},
       {8, 4, 5, 8, 5, 3, 3, 5, 1, -1, -1, -1, -1, -1, -1, -1}, {0, 4, 5, 1, 0, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
       {8, 4, 5, 8, 5, 3, 9, 0, 5, 0, 3, 5, -1, -1, -1, -1}, {9, 4, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
       {4, 11, 7, 4, 9, 11, 9, 10, 11, -1, -1, -1, -1, -1, -1, -1}, {0, 8, 3, 4, 9, 7, 9, 11, 7, 9, 10, 11, -1, -1, -1, -1},
       {1, 10, 11, 1, 11, 4, 1, 4, 0, 7, 4, 11, -1, -1, -1, -1}, {3, 1, 4, 3, 4, 8, 1, 10, 4, 7, 4, 11, 10, 11, 4, -1},
       {4, 11, 7, 9, 11, 4, 9, 2, 11, 9, 1, 2, -1, -1, -1, -1}, {9, 7, 4, 9, 11, 7, 9, 1, 11, 2, 11, 1, 0, 8, 3, -1},
       {11, 7, 4, 11, 4, 2, 2, 4, 0, -1, -1, -1, -1, -1, -1, -1}, {11, 7, 4, 11, 4, 2, 8, 3, 4, 3, 2, 4, -1, -1, -1, -1},
       {2, 9, 10, 2, 7, 9, 2, 3, 7, 7, 4, 9, -1, -1, -1, -1}, {9, 10, 7, 9, 7, 4, 10, 2, 7, 8, 7, 0, 2, 0, 7, -1},
       {3, 7, 10, 3, 10, 2, 7, 4, 10, 1, 10, 0, 4, 0, 10, -1}, {1, 10, 2, 8, 7, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
       {4, 9, 1, 4, 1, 7, 7, 1, 3, -1, -1, -1, -1, -1, -1, -1}, {4, 9, 1, 4, 1, 7, 0, 8, 1, 8, 7, 1, -1, -1, -1, -1},
       {4, 0, 3, 7, 4, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {4, 8, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
       {9, 10, 8, 10, 11, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {3, 0, 9, 3, 9, 11, 11, 9, 10, -1, -1, -1, -1, -1, -1, -1},
       {0, 1, 10, 0, 10, 8, 8, 10, 11, -1, -1, -1, -1, -1, -1, -1}, {3, 1, 10, 11, 3, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
       {1, 2, 11, 1, 11, 9, 9, 11, 8, -1, -1, -1, -1, -1, -1, -1}, {3, 0, 9, 3, 9, 11, 1, 2, 9, 2, 11, 9, -1, -1, -1, -1},
       {0, 2, 11, 8, 0, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {3, 2, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
       {2, 3, 8, 2, 8, 10, 10, 8, 9, -1, -1, -1, -1, -1, -1, -1}, {9, 10, 2, 0, 9, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
       {2, 3, 8, 2, 8, 10, 0, 1, 8, 1, 10, 8, -1, -1, -1, -1}, {1, 10, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
       {1, 3, 8, 9, 1, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {0, 9, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
       {0, 3, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}};

    const unsigned int
      nx = (unsigned int)((x1-x0+1)/resx), nxm1 = nx-1,
      ny = (unsigned int)((y1-y0+1)/resy), nym1 = ny-1,
      nz = (unsigned int)((z1-z0+1)/resz), nzm1 = nz-1;

    if (!nxm1 || !nym1 || !nzm1) return;

    CImg<int> indices1(nx,ny,1,3,-1), indices2(indices1);
    CImg<float> values1(nx,ny), values2(nx,ny);
    float X = 0, Y = 0, Z = 0, nX = 0, nY = 0, nZ = 0;

    // Fill the first plane with function values
    Y=y0;
    cimg_forY(values1,y) {
      X = x0;
      cimg_forX(values1,x) { values1(x,y) = (float)func(X,Y,z0); X+=resx; }
      Y+=resy;
    }

    // Run Marching Cubes algorithm
    Z = z0; nZ = Z + resz;
    for (unsigned int zi=0; zi<nzm1; ++zi, Z=nZ, nZ+=resz) {
      Y = y0; nY = Y + resy;
      indices2.fill(-1);
      for (unsigned int yi=0, nyi=1; yi<nym1; ++yi, ++nyi, Y=nY, nY+=resy) {
        X = x0; nX = X + resx;
        for (unsigned int xi=0, nxi=1; xi<nxm1; ++xi, ++nxi, X=nX, nX+=resx) {

          // Determine cube configuration
          const float
            val0 = values1(xi,yi), val1 = values1(nxi,yi), val2 = values1(nxi,nyi), val3 = values1(xi,nyi),
            val4 = values2(xi,yi) = (float)func(X,Y,nZ),
            val5 = values2(nxi,yi) = (float)func(nX,Y,nZ),
            val6 = values2(nxi,nyi) = (float)func(nX,nY,nZ),
            val7 = values2(xi,nyi) = (float)func(X,nY,nZ);

          const unsigned int configuration =
            (val0<isovalue?1:0)  | (val1<isovalue?2:0)  | (val2<isovalue?4:0)  | (val3<isovalue?8:0) |
            (val4<isovalue?16:0) | (val5<isovalue?32:0) | (val6<isovalue?64:0) | (val7<isovalue?128:0),
            edge = edges[configuration];

          // Compute intersection points
          if (edge) {
            if ((edge&1) && indices1(xi,yi,0)<0) {
              const float Xi = X + (isovalue-val0)*resx/(val1-val0);
              indices1(xi,yi,0) = points.size;
              points.insert(CImg<tp>::vector(Xi,Y,Z));
            }
            if ((edge&2) && indices1(nxi,yi,1)<0) {
              const float Yi = Y + (isovalue-val1)*resy/(val2-val1);
              indices1(nxi,yi,1) = points.size;
              points.insert(CImg<tp>::vector(nX,Yi,Z));
            }
            if ((edge&4) && indices1(xi,nyi,0)<0) {
              const float Xi = X + (isovalue-val3)*resx/(val2-val3);
              indices1(xi,nyi,0) = points.size;
              points.insert(CImg<tp>::vector(Xi,nY,Z));
            }
            if ((edge&8) && indices1(xi,yi,1)<0) {
              const float Yi = Y + (isovalue-val0)*resy/(val3-val0);
              indices1(xi,yi,1) = points.size;
              points.insert(CImg<tp>::vector(X,Yi,Z));
            }
            if ((edge&16) && indices2(xi,yi,0)<0) {
              const float Xi = X + (isovalue-val4)*resx/(val5-val4);
              indices2(xi,yi,0) = points.size;
              points.insert(CImg<tp>::vector(Xi,Y,nZ));
            }
            if ((edge&32) && indices2(nxi,yi,1)<0) {
              const float Yi = Y + (isovalue-val5)*resy/(val6-val5);
              indices2(nxi,yi,1) = points.size;
              points.insert(CImg<tp>::vector(nX,Yi,nZ));
            }
            if ((edge&64) && indices2(xi,nyi,0)<0) {
              const float Xi = X + (isovalue-val7)*resx/(val6-val7);
              indices2(xi,nyi,0) = points.size;
              points.insert(CImg<tp>::vector(Xi,nY,nZ));
            }
            if ((edge&128) && indices2(xi,yi,1)<0)  {
              const float Yi = Y + (isovalue-val4)*resy/(val7-val4);
              indices2(xi,yi,1) = points.size;
              points.insert(CImg<tp>::vector(X,Yi,nZ));
            }
            if ((edge&256) && indices1(xi,yi,2)<0) {
              const float Zi = Z+ (isovalue-val0)*resz/(val4-val0);
              indices1(xi,yi,2) = points.size;
              points.insert(CImg<tp>::vector(X,Y,Zi));
            }
            if ((edge&512) && indices1(nxi,yi,2)<0)  {
              const float Zi = Z + (isovalue-val1)*resz/(val5-val1);
              indices1(nxi,yi,2) = points.size;
              points.insert(CImg<tp>::vector(nX,Y,Zi));
            }
            if ((edge&1024) && indices1(nxi,nyi,2)<0) {
              const float Zi = Z + (isovalue-val2)*resz/(val6-val2);
              indices1(nxi,nyi,2) = points.size;
              points.insert(CImg<tp>::vector(nX,nY,Zi));
            }
            if ((edge&2048) && indices1(xi,nyi,2)<0) {
              const float Zi = Z + (isovalue-val3)*resz/(val7-val3);
              indices1(xi,nyi,2) = points.size;
              points.insert(CImg<tp>::vector(X,nY,Zi));
            }

            // Create triangles
            for (int *triangle=triangles[configuration]; *triangle!=-1; ) {
              const unsigned int p0 = *(triangle++), p1 = *(triangle++), p2 = *(triangle++);
              const tf
                i0 = (tf)(_marching_cubes_indice(p0,indices1,indices2,xi,yi,nxi,nyi)),
                i1 = (tf)(_marching_cubes_indice(p1,indices1,indices2,xi,yi,nxi,nyi)),
                i2 = (tf)(_marching_cubes_indice(p2,indices1,indices2,xi,yi,nxi,nyi));
              if (invert_faces) primitives.insert(CImg<tf>::vector(i0,i1,i2));
              else primitives.insert(CImg<tf>::vector(i0,i2,i1));
            }
          }
        }
      }
      cimg::swap(values1,values2);
      cimg::swap(indices1,indices2);
    }
  }

  // Inner routine used by the Marching square algorithm
  template<typename t> inline int _marching_squares_indice(const unsigned int edge, const CImg<t>& indices1, const CImg<t>& indices2,
                                                           const unsigned int x, const unsigned int nx) {
    switch (edge) {
    case 0: return (int)indices1(x,0);
    case 1: return (int)indices1(nx,1);
    case 2: return (int)indices2(x,0);
    case 3: return (int)indices1(x,1);
    }
    return 0;
  }

  //! Polygonize an implicit 2D function by the marching squares algorithm
  template<typename tfunc, typename tp, typename tf>
  inline void marching_squares(const tfunc& func, const float isovalue,
                               const float x0, const float y0,
                               const float x1, const float y1,
                               const float resx, const float resy,
                               CImgList<tp>& points, CImgList<tf>& primitives) {

    static unsigned int edges[16] = { 0x0, 0x9, 0x3, 0xa, 0x6, 0xf, 0x5, 0xc, 0xc, 0x5, 0xf, 0x6, 0xa, 0x3, 0x9, 0x0 };
    static int segments[16][4] = { { -1,-1,-1,-1 }, { 0,3,-1,-1 }, { 0,1,-1,-1 }, { 1,3,-1,-1 },
                                   { 1,2,-1,-1 },   { 0,1,2,3 },   { 0,2,-1,-1 }, { 2,3,-1,-1 },
                                   { 2,3,-1,-1 },   { 0,2,-1,-1},  { 0,3,1,2 },   { 1,2,-1,-1 },
                                   { 1,3,-1,-1 },   { 0,1,-1,-1},  { 0,3,-1,-1},  { -1,-1,-1,-1 } };
    const unsigned int
      nx = (unsigned int)((x1-x0+1)/resx), nxm1 = nx-1,
      ny = (unsigned int)((y1-y0+1)/resy), nym1 = ny-1;

    if (!nxm1 || !nym1) return;

    CImg<int> indices1(nx,1,1,2,-1), indices2(nx,1,1,2);
    CImg<float> values1(nx), values2(nx);
    float X = 0, Y = 0, nX = 0, nY = 0;

    // Fill first line with values
    cimg_forX(values1,x) { values1(x) = (float)func(X,Y); X+=resx; }

    // Run the marching squares algorithm
    Y = y0; nY = Y + resy;
    for (unsigned int yi=0, nyi=1; yi<nym1; ++yi, ++nyi, Y=nY, nY+=resy) {
      X = x0; nX = X + resx;
      indices2.fill(-1);
      for (unsigned int xi=0, nxi=1; xi<nxm1; ++xi, ++nxi, X=nX, nX+=resx) {

        // Determine cube configuration
        const float
          val0 = values1(xi), val1 = values1(nxi),
          val2 = values2(nxi) = (float)func(nX,nY),
          val3 = values2(xi) = (float)func(X,nY);

        const unsigned int configuration = (val0<isovalue?1:0)  | (val1<isovalue?2:0)  | (val2<isovalue?4:0)  | (val3<isovalue?8:0),
          edge = edges[configuration];

        // Compute intersection points
        if (edge) {
          if ((edge&1) && indices1(xi,0)<0) {
            const float Xi = X + (isovalue-val0)*resx/(val1-val0);
            indices1(xi,0) = points.size;
            points.insert(CImg<tp>::vector(Xi,Y));
          }
          if ((edge&2) && indices1(nxi,1)<0) {
            const float Yi = Y + (isovalue-val1)*resy/(val2-val1);
            indices1(nxi,1) = points.size;
            points.insert(CImg<tp>::vector(nX,Yi));
          }
          if ((edge&4) && indices2(xi,0)<0) {
            const float Xi = X + (isovalue-val3)*resx/(val2-val3);
            indices2(xi,0) = points.size;
            points.insert(CImg<tp>::vector(Xi,nY));
          }
          if ((edge&8) && indices1(xi,1)<0) {
            const float Yi = Y + (isovalue-val0)*resy/(val3-val0);
            indices1(xi,1) = points.size;
            points.insert(CImg<tp>::vector(X,Yi));
          }

          // Create segments
          for (int *segment=segments[configuration]; *segment!=-1; ) {
            const unsigned int p0 = *(segment++), p1 = *(segment++);
            const tf
              i0 = (tf)(_marching_squares_indice(p0,indices1,indices2,xi,nxi)),
              i1 = (tf)(_marching_squares_indice(p1,indices1,indices2,xi,nxi));
            primitives.insert(CImg<tf>::vector(i0,i1));
          }
        }
      }
      values1.swap(values2);
      indices1.swap(indices2);
    }
  }

  // End of cimg:: namespace
}

  // End of cimg_library:: namespace
}

#ifdef cimg_use_visualcpp6
#undef std
#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

#endif
// Local Variables:
// mode: c++
// End:
